在C#与WinUI3中创建可复用Flyout并在多代码后置中实例化
解决跨类复用CommandBarFlyout并绑定动态状态的方案
1. 正确声明全局可复用的CommandBarFlyout
资源字典不能直接以CommandBarFlyout为根元素,必须包裹在<ResourceDictionary>中,你可以选择两种实现方式:
方式一:添加到App.xaml的全局资源
打开App.xaml,在<Application.Resources>节点内添加以下代码:
<Application.Resources> <BooleanToVisibilityConverter x:Key="BoolToVis" /> <CommandBarFlyout x:Key="ImageContextMenu" AlwaysExpanded="True" x:Shared="False"> <!-- 关键:确保每次获取都是新实例,避免状态串用 --> <CommandBarFlyout.SecondaryCommands> <AppBarButton Label="A" Visibility="{Binding IsButtonAVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="B" Visibility="{Binding IsButtonBVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="C" Visibility="{Binding IsButtonCVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="D" Visibility="{Binding IsButtonDVisible, Converter={StaticResource BoolToVis}}"/> </CommandBarFlyout.SecondaryCommands> </CommandBarFlyout> </Application.Resources>
方式二:使用独立资源字典文件
- 新建资源字典文件(如
FlyoutResources.xaml),内容如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <BooleanToVisibilityConverter x:Key="BoolToVis" /> <CommandBarFlyout x:Key="ImageContextMenu" AlwaysExpanded="True" x:Shared="False"> <CommandBarFlyout.SecondaryCommands> <AppBarButton Label="A" Visibility="{Binding IsButtonAVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="B" Visibility="{Binding IsButtonBVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="C" Visibility="{Binding IsButtonCVisible, Converter={StaticResource BoolToVis}}"/> <AppBarButton Label="D" Visibility="{Binding IsButtonDVisible, Converter={StaticResource BoolToVis}}"/> </CommandBarFlyout.SecondaryCommands> </CommandBarFlyout> </ResourceDictionary>
- 在
App.xaml中合并该资源字典:
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="FlyoutResources.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
2. 定义图片的状态类
创建实现INotifyPropertyChanged的状态类,用于控制按钮可见性和承载交互逻辑:
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Input; public class ImageFlyoutState : INotifyPropertyChanged { private bool _isButtonAVisible; public bool IsButtonAVisible { get => _isButtonAVisible; set => SetField(ref _isButtonAVisible, value); } private bool _isButtonBVisible; public bool IsButtonBVisible { get => _isButtonBVisible; set => SetField(ref _isButtonBVisible, value); } private bool _isButtonCVisible; public bool IsButtonCVisible { get => _isButtonCVisible; set => SetField(ref _isButtonCVisible, value); } private bool _isButtonDVisible; public bool IsButtonDVisible { get => _isButtonDVisible; set => SetField(ref _isButtonDVisible, value); } // 可选:用ICommand绑定按钮点击,替代代码后置的回调 public ICommand ButtonACommand { get; set; } public ICommand ButtonBCommand { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } }
3. 代码后置中绑定Flyout到图片并设置状态/回调
方式一:使用ICommand绑定点击推荐
// 为每张图片创建独立状态实例 var imageState = new ImageFlyoutState { IsButtonAVisible = true, // 根据图片需求设置可见性 IsButtonBVisible = false, ButtonACommand = new RelayCommand(() => { // 按钮A的点击逻辑 }), ButtonBCommand = new RelayCommand(() => { // 按钮B的点击逻辑 }) }; // 获取全局资源中的Flyout实例(因为x:Shared="False",每次都是新的) if (Application.Current.Resources.TryGetValue("ImageContextMenu", out var flyoutObj) && flyoutObj is CommandBarFlyout flyout) { // 将状态类绑定到Flyout的DataContext,让可见性绑定生效 flyout.DataContext = imageState; // 将Flyout关联到目标图片 FlyoutBase.SetAttachedFlyout(image, flyout); }
注:RelayCommand是常见的ICommand实现,你可以自行实现或使用MvvmLight等社区库的版本
方式二:代码后置直接绑定点击事件
如果坚持用代码后置的回调,可参考以下写法:
var imageState = new ImageFlyoutState { IsButtonAVisible = true, IsButtonCVisible = true }; if (Application.Current.Resources.TryGetValue("ImageContextMenu", out var flyoutObj) && flyoutObj is CommandBarFlyout flyout) { flyout.DataContext = imageState; // 遍历SecondaryCommands为按钮绑定点击事件 foreach (var cmd in flyout.SecondaryCommands) { if (cmd is AppBarButton btn) { switch (btn.Label) { case "A": btn.Click += ButtonA_Click; break; case "B": btn.Click += ButtonB_Click; break; // 其他按钮同理 } } } FlyoutBase.SetAttachedFlyout(image, flyout); } // 点击事件处理函数 private void ButtonA_Click(object sender, RoutedEventArgs e) { // 按钮A的逻辑 }
关键注意事项
- x:Shared="False":必须添加该属性,否则所有图片会共用同一个Flyout实例,导致状态串用。
- DataContext绑定:每个Flyout实例的DataContext必须设置为对应图片的独立状态对象,确保可见性绑定正确对应。
- BooleanToVisibilityConverter:用于将布尔值转换为UI的Visibility枚举值,是绑定可见性的标准方式。
内容的提问来源于stack exchange,提问作者John Glen
相关产品推荐
相关产品推荐

