【问题标题】:WPF binding to a global variable to update UIWPF 绑定到全局变量以更新 UI
【发布时间】:2018-03-12 05:55:04
【问题描述】:

假设我有两个窗口和一个托盘图标上下文菜单。每个窗口都有一个切换按钮,上下文菜单有一个可检查的菜单项。所有三个控件都旨在显示和切换相同值的状态。

在这种情况下,如何将三个控件中的 IsChecked 绑定到一个全局变量,当其中一个控件被选中/取消选中时,其他控件将相应地更新?我应该只调用还是有 MVVM 解决方案?我是 WPF 新手,所以我不确定完成此任务的最佳/最正确方法。

【问题讨论】:

    标签: c# .net wpf mvvm


    【解决方案1】:

    假设您有 WindowA、WindowB、...、WindowN,并假设它们都是不同的类型。

    创建一个类,比如说 CommonState,它封装了所有常见的属性、命令等并实现 INotifyPropertyChanged

    public class CommonState : INotifyPropertyChanged
    {
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private bool _isChecked;
    
        public bool IsChecked
        {
            get { return _isChecked; }
            set
            {
                if (value != _isChecked)
                {
                    _isChecked = value;
                    OnPropertyChanged();
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    然后声明一个接口:

    public interface ICommonStateWindow
    {
        CommonState { get; set; }
    }
    

    让每个窗口都实现这个接口:

    public partial class WindowA : Window, ICommonState
    {
        public WindowA()
        {
            InitializeComponent();
        }
    
        // This property will be injected, do not re-assign
        public CommonState CommonState { get; set; }
    }
    

    在显示之前在每个窗口中注入公共状态,例如:

    public partial class App : Application
    {
        private CommonState _state;
    
        protected override void OnStartup(StartupEventArgs e)
        {
            _state = new CommonState() {IsChecked = true};
    
            var wndA = new WindowA() { CommonState = _state };
            var wndB = new WindowB() { CommonState = _state };
    
            wndA.Show();
            wndB.Show();
        }
    }
    

    请记住在某个长期存在的对象(如应用程序或主窗口)中至少保留一个对创建的 CommonState 的引用,这样它就不会在某些时候被垃圾收集。

    在 XAML 中,您应该使用 RelativeSource 进行绑定,以便您创建的每种新类型的窗口都可以拥有自己独立的 ViewModel (DataContext):

    <Window x:Class="Example.WindowA"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="WindowA" Height="300" Width="300">
        <Grid>
            <CheckBox IsChecked="{Binding CommonState.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
        </Grid>
    </Window>
    

    我演示的示例不是唯一的方法,我不会说“最好的”,但它解决了以下问题:

    1. 封装公共(共享)状态
    2. 在不同的窗口实例(或类型)之间同步状态
    3. 允许独立于窗口实现扩展 CommonState(仅需要更新 XAML)

    另一种可能的解决方案是将 CommonState 的单例实例注册到静态暴露的控制容器反转 (IoC) 中,并让每个具体窗口的 ViewModel 获得一个实例。这样,您将避免注射步骤。对于小型项目来说,这将是一个过大的杀伤力

    我有人试图运行上面的代码,记得从 App.xaml 中删除 StartupUri="MainWindow.xaml"

    【讨论】:

    • 到目前为止,这在两个窗口之间效果很好。如何将 CommonState 注入 XAML 定义的 ContextMenu?我正在使用 Hardcodet.Wpf.TaskbarNotification TaskbarIcon 库。 ContextMenu 在 XAML ResourceDictionary 中定义。 TaskbarIcon 由 FindResource 在 App.OnStartup 期间创建。
    • 添加 ICommand CommandA { get; 等属性放; } 在 CommonState 中并初始化命令。然后像这样绑定命令:
    • 我收到一个错误:找不到引用'RelativeSource FindAncestor,AncestorType='System.Windows.Window',AncestorLevel='1''的绑定源。绑定表达式:路径=CommonState.IsCheck1;数据项=空;目标元素是'MenuItem'(名称='mnuCheck1');目标属性是“命令”(类型“ICommand”)。这里有更多细节: ... public partial class NotifyIconResources : ResourceDictionary, ICommonState { (MenuEventHandlers) } RD 获得 CommonState 参考?
    【解决方案2】:

    您可以添加到您的代码隐藏 bool IsChecked 属性并将其用于您想要的所有组件。您可以将其组件的事件方法更改为 true 或 false。

    【讨论】:

    • 不太关注。这一切是如何联系在一起的,因此一个组件的更改会更新所有其他组件?
    猜你喜欢
    • 2010-12-01
    • 2013-07-21
    • 2012-11-09
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    • 2011-03-31
    • 2016-12-17
    • 1970-01-01
    相关资源
    最近更新 更多