I was reading this article and could not help but wonder the same thing.
Is there a way to databind a menu Flyout
control?
Yes.
I put together a simple solution for developers who desire this functionality. It uses an attached property to identify the ItemsSource and the ItemTemplate for a Flyout control. If the developer elects to use a MenuFlyoutItem
or something else, it is up to them.
Here's the attached property:
public class BindableFlyout : DependencyObject
{
#region ItemsSource
public static IEnumerable GetItemsSource(DependencyObject obj)
{
return obj.GetValue(ItemsSourceProperty) as IEnumerable;
}
public static void SetItemsSource(DependencyObject obj, IEnumerable value)
{
obj.SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource", typeof(IEnumerable),
typeof(BindableFlyout), new PropertyMetadata(null, ItemsSourceChanged));
private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ Setup(d as Windows.UI.Xaml.Controls.Flyout); }
#endregion
#region ItemTemplate
public static DataTemplate GetItemTemplate(DependencyObject obj)
{
return (DataTemplate)obj.GetValue(ItemTemplateProperty);
}
public static void SetItemTemplate(DependencyObject obj, DataTemplate value)
{
obj.SetValue(ItemTemplateProperty, value);
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.RegisterAttached("ItemTemplate", typeof(DataTemplate),
typeof(BindableFlyout), new PropertyMetadata(null, ItemsTemplateChanged));
private static void ItemsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ Setup(d as Windows.UI.Xaml.Controls.Flyout); }
#endregion
private static async void Setup(Windows.UI.Xaml.Controls.Flyout m)
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
return;
var s = GetItemsSource(m);
if (s == null)
return;
var t = GetItemTemplate(m);
if (t == null)
return;
var c = new Windows.UI.Xaml.Controls.ItemsControl
{
ItemsSource = s,
ItemTemplate = t,
};
var n = Windows.UI.Core.CoreDispatcherPriority.Normal;
Windows.UI.Core.DispatchedHandler h = () => m.Content = c;
await m.Dispatcher.RunAsync(n, h);
}
}
And, here's sample usage.
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Label="AppBarButton">
<AppBarButton.Flyout>
<Flyout local:BindableFlyout.ItemsSource="{Binding MenuItems}">
<local:BindableFlyout.ItemTemplate>
<DataTemplate>
<MenuFlyoutItem Text="{Binding Text}" />
</DataTemplate>
</local:BindableFlyout.ItemTemplate>
</Flyout>
</AppBarButton.Flyout>
<AppBarButton.Icon>
<SymbolIcon/>
</AppBarButton.Icon>
</AppBarButton>
</CommandBar>
</Page.BottomAppBar>
I will be maintaining this code here.
Looks like this:
I hope this helps you.
Best of luck!
Even though the original question was asked ages ago I'll post the solution I've found, as someone else might find it useful.
Jerry's solution has a serious flaw: the MenuFlyout isn't closed when you click an item and I've found it exceedingly difficult to do so, as it seems to be (nearly?) impossible to get a reference to the Flyout from inside the DataTemplate to close it.
I've come up with this solution that subclasses MenuFlyout:
public class BindableFlyout : MenuFlyout
{
public ICollection<ContextMenuCommand> ItemsSource
{
get { return (ICollection<ContextMenuCommand>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ICollection<ContextMenuCommand>), typeof(BindableFlyout), new PropertyMetadata(null, (DependencyObject o, DependencyPropertyChangedEventArgs args) =>
{
Setup(o as BindableFlyout);
}
));
private static async void Setup(BindableFlyout menuFlyout)
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
return;
if (menuFlyout.ItemsSource == null)
return;
await menuFlyout.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
menuFlyout.Items.Clear();
foreach (var menuItem in menuFlyout.ItemsSource)
{
menuFlyout.Items.Add(new MenuFlyoutItem()
{
Text = menuItem.Text,
Command = menuItem.Command
});
}
});
}
}
public class ContextMenuCommand
{
public ContextMenuCommand(ICommand command, string text)
{
Command = command;
Text = text;
}
public string Text
{
get; private set;
}
public ICommand Command
{
get; private set;
}
}
The snippet above doesn't listen to changes of the ItemsSource, but you can easily adapt the class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With