c# - 指定 DataContext (WPF) 的正确方法

标签 c# wpf mvvm

我刚开始使用 WPF。在我的新应用程序中,我首先实现了带有上下文菜单的通知图标。接下来我开始构建 MVVM 框架,发现新的变化影响了已经实现的代码。

我正在使用来自 Hardcodet 的 NotifyIcon .我的初始版本是这样的:

<Window x:Class="ScanManager.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
        xmlns:tb="http://www.hardcodet.net/taskbar" 
        xmlns:commands="clr-namespace:ScanManager.Commands"
        Title="Scan" Height="542" Width="821">
    <Grid Visibility="Visible" Loaded="form_Loaded">
        ...
        <tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
            <tb:TaskbarIcon.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding}" />
                    <MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding}" />
                </ContextMenu>
            </tb:TaskbarIcon.ContextMenu>
        </tb:TaskbarIcon>
        <Button Name="hideButton"  Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
    </Grid>
</Window>

接下来我根据文章 The World's Simplest C# WPF MVVM Example 开始合并 MVVM 模式.示例项目添加了指向 ViewModel 类的 DataContext。

<Window.DataContext>
    <ViewModels:Presenter/>
</Window.DataContext>

此更改影响了通知图标的工作方式。简而言之,ShowMainWindowCommand 和 HideMainWindowCommand 对象的覆盖方法 ICommand.CanExecute(object parameter)ICommand.Execute(object parameter) 开始接收对象 PresenterWindow.DataContext 中定义,而不是原始的 Hardcodet.Wpf.TaskbarNotification.TaskbarIcon。我猜这是因为添加的 DataContext 会影响 CommandParameter{Binding} 值。

Execute 方法要求参数为 TaskbarIcon,以便识别父 Window 对象,然后可以将其设置为显示或隐藏。

我尝试解决它的方法是将除 TaskbarIcon 之外的所有元素从 Window 移动到 UserControl,在 Grid 下并将 DataContext 应用于 Grid

<UserControl x:Class="ScanManager.Views.SControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    ...
    d:DataContext="{d:DesignInstance ViewModels:Presenter}">

    <Grid Visibility="Visible">

        <Grid.DataContext>
            <ViewModels:Presenter/>
        </Grid.DataContext>

        <Button Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
        ...
    </Grid>
</UserControl>

它解决了通知图标的问题,但我想知道这是否是解决问题的正确方法。我认为另一种方法可能是在添加 DataContext 后将原始版本的 MenuItem 中的 CommandParameter 设置为适当的值,但是我有很难弄清楚这一点。

作为下一步,我尝试将 UserControl 对象的 DataContext 转换为 INotifyPropertyChanged 以订阅 PropertyChanged 事件,但是 DataContext 属性作为 null 出现,推测是因为它仅设置为 Grid 而不是 用户控件:

    INotifyPropertyChanged viewModel = (INotifyPropertyChanged)this.DataContext;

任何有关将这些部分正确组合在一起的指导将不胜感激。

编辑

拒绝访问,这些选项对 Button 元素很有帮助。 如果我想回到顶部的初始版本,MenuItem 元素使用 Command="{commands:ShowMainWindowCommand}"CommandParameter="{Binding} ”。如果我添加 Window.DataContext,是否可以对 MenuItem 的 Command/CommandParameter 属性进行更改以引用它们之前引用的内容(我假设,父元素)?我尝试了 CommandParameter="{Binding Path=mainTaskbarIcon}" 但它不起作用,这意味着,与以前一样,Execute/CanExecute 接收到 null。

<Window x:Class="ScanManager.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
        xmlns:tb="http://www.hardcodet.net/taskbar" 
        xmlns:commands="clr-namespace:ScanManager.Commands"
        Title="Scan" Height="542" Width="821">

    <Window.DataContext>
        <ViewModels:Presenter/>
    </Window.DataContext>

    <Grid Visibility="Visible" Loaded="form_Loaded">
        ...
        <tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
            <tb:TaskbarIcon.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
                    <MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
                </ContextMenu>
            </tb:TaskbarIcon.ContextMenu>
        </tb:TaskbarIcon>
        ...
    </Grid>
</Window>

最佳答案

当您设置数据上下文时,它也会传播到内部控件,是的,它会影响绑定(bind)上下文。无需创建 UserControl,因为它不会阻止上下文传播。为了防止它更改控件的数据上下文或指定绑定(bind)源。例如,如果您想更改按钮的上下文。

使用 DataContext 覆盖的方法:

<Grid Visibility="Visible">

<Grid.Resources>
   <ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>

<Button DataContext="{StaticResource buttonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>

指定来源的方法:

<Grid.Resources>
   <ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>

<Button Name="hideButton" Command="{Binding Source={StaticResource buttonContext}, Path=HideMainWindowCommand}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>

或者您也可以在您的根 viewModel 中拥有 ButtonContext 属性并以这种方式解析它:

<Button DataContext="{Binding ButtonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>

如何订阅DataContextChanged事件:

 public MainWindow()
        {
            InitializeComponent();
            DataContextChanged += MainWindow_DataContextChanged;

处理事件:

private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (e.OldValue != null && e.OldValue is INotifyPropertyChanged)
    {
        ((INotifyPropertyChanged)e.OldValue).PropertyChanged -= MainWindow_PropertyChanged;
    }
    if (e.NewValue != null && e.NewValue is INotifyPropertyChanged)
    {
        ((INotifyPropertyChanged)e.NewValue).PropertyChanged += MainWindow_PropertyChanged;
    }
}

private void MainWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
 ...
}

关于c# - 指定 DataContext (WPF) 的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53095124/

相关文章:

.net - WPF 初学者技巧

wpf - XAML TextBlock 和运行绑定(bind)

wpf - 将 View 模型与模型相结合

wpf - 在WPF MVVM项目的解决方案资源管理器中移动XAML文件

c# - 如何将 SelectedListViewItemCollection 转换为 ListViewItemCollection

c# - 为什么不能同时使用 :base() and :this() in a C# constructor

c# - DataSet 行/列查找的速度?

c# - 使用 c# 和 xUnit 在 Visual Studio 2012 的文本资源管理器中显示测试类名称

c# - 在 View 模型中使用 ICommand 可以吗

c# - 如何使用 ReactiveUI 在 ViewModel 停用时正确取消任务?