I was reading this article and could not help but wonder the same thing.
Is there a way to databind a menu Flyout
control?
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!
SelectedItem
? Meaning the MenuItem triggers calling control to change based off of what I've selected in this bindable Flyout? –
Fowliang 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.
It works for me. I hope I didn't miss a thing.
class CustomCommand : ICommand
{
public ICommand CommandObject { get { return this; } }
public String CommandName { get; private set; }
public CustomCommand(String name):base()
{
this.CommandName = name;
}
}
class EncapsulateOrDecoratorObjectForContextMenu
{
private object baseObject;
// chaned properties to the baseObject
public List<CustomCommand> AvailableCommands { get; set; }
public EncapsulateOrDecoratorObjectForContextMenu(object baseObject, List<CustomCommand> commands)
{
this.baseObject = baseObject;
this.AvailableCommands = commands;
}
}
class SomePage: Page
{
private MenuFlyout mFlyout;
public SomePage()
{
// I don't know why, but it's to be here... unless UI/design go crazy
this.mFlyout = new MenuFlyout();
}
private void Grid_Holding(object sender, HoldingRoutedEventArgs e)
{
if (e.OriginalSource is FrameworkElement && (e.OriginalSource as FrameworkElement).DataContext is EncapsulateOrDecoratorObjectForContextMenu)
{
// Only the property is 'readonly', not the List<menuItem> itself, so...
this.mFlyout.Items.Clear();
MenuFlyoutItem menuItem;
foreach (CustomCommand command in ((e.OriginalSource as FrameworkElement).DataContext as EncapsulateOrDecoratorObjectForContextMenu).AvailableCommands)
{
menuItem = new MenuFlyoutItem();
menuItem.Text = command.CommandName;
menuItem.Command = command.CommandObject;
this.mFlyout.Items.Add(menuItem);
}
FrameworkElement senderElement = sender as FrameworkElement;
this.mFlyout.ShowAt(senderElement);
}
}
}
© 2022 - 2024 — McMap. All rights reserved.