【问题标题】:WPF C# UserControl DependencyProperty Command Binding is nullWPF C# UserControl DependencyProperty 命令绑定为空
【发布时间】:2017-02-27 15:08:51
【问题描述】:

我创建了一个UserControl 作为文件浏览工具,我想将Commands 实现为DependencyProperties 以进行加载和保存,以便我可以从我的ViewModel 绑定Commands 以处理它们。

现在的问题是,如果我使用预定义的 CommandsOpenSave 并在我的 Window 中处理它们,它可以工作,但如果我使用 Bindingsfrom 我的 ViewModel 这些 Commandsnull...

以下代码是一个示例程序,我删除了对我的问题不重要的所有内容,因为它的代码太多了。


用户控制

XAML

<UserControl x:Class="WpfApplication1.TestControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfApplication1"
         xmlns:models="clr-namespace:WpfApplication1.Models"
         Height="21" Width="80" Margin="2">
<UserControl.Resources>
    <models:UserControlViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<UserControl.DataContext>
    <Binding Source="{StaticResource ViewModel}"/>
</UserControl.DataContext>
<Grid>
    <Button Content="_Load" IsDefault="True"
            Command="{Binding Path=ExecuteCommand, Source={StaticResource ViewModel}}"
            CommandParameter="{Binding Path=LoadCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType ={x:Type local:TestControl}}}"/>
</Grid>

代码隐藏

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class TestControl : UserControl
    {
        public static readonly DependencyProperty LoadCommandProperty = DependencyProperty.Register(nameof(LoadCommand), typeof(ICommand), typeof(TestControl), new PropertyMetadata(null));
        public ICommand LoadCommand
        {
            get { return (ICommand)GetValue(LoadCommandProperty); }
            set { SetValue(LoadCommandProperty, value); }
        }

        public TestControl()
        {
            InitializeComponent();
        }
    }
}

视图模型

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Prism.Commands;

namespace WpfApplication1.Models
{
    public class UserControlViewModel
        : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public DelegateCommand<ICommand> ExecuteCommand { get; }
        private void ExecuteCommand_Executed(ICommand cmd) => cmd?.Execute("C:\\Test.txt");

        private void Notify([CallerMemberName] string name = null)
        {
            if (name != null)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        public UserControlViewModel()
        {
            ExecuteCommand = new DelegateCommand<ICommand>(ExecuteCommand_Executed);
        }
    }
}

窗口

XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:models="clr-namespace:WpfApplication1.Models"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200">
    <Window.Resources>
        <models:MainWindowViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Window.DataContext>
        <Binding Source="{StaticResource ViewModel}"/>
    </Window.DataContext>
    <Window.CommandBindings>
        <CommandBinding Command="Open" Executed="CommandBinding_Executed"/>
    </Window.CommandBindings>
    <StackPanel VerticalAlignment="Center">
        <local:TestControl LoadCommand="{Binding Path=OpenCommand, Source={StaticResource ViewModel}}"/>
        <local:TestControl LoadCommand="Open"/>
    </StackPanel>
</Window>

代码隐藏

using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"Window: {e.Parameter.ToString()}");
        }
    }
}

视图模型

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using Prism.Commands;

namespace WpfApplication1.Models
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public DelegateCommand<string> OpenCommand { get; }
        private void OpenCommand_Executed(string file)
        {
            MessageBox.Show($"Model: {file}");
        }

        private void Notify([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        public MainWindowViewModel()
        {
            OpenCommand = new DelegateCommand<string>(OpenCommand_Executed);
        }
    }
}

该窗口包含一个预定义的Open Command,这种方式有效,但Binding 无效。

要运行此应用程序,您需要 Prism.Wpf NuGet 包。

【问题讨论】:

  • 收到 MVCE 后给我回复。了解全貌后应该不会那么难。
  • 您没有在 ViewModel 中分配命令。如果您使用的是发布的,则为空。此外,此时命令工作应该存在于 ViewModel 中,并且您仍在为命令使用代码。基本上,如果您想向控件添加命令,那么只需在包装浏览器的自定义控件中创建 ICommand(复制 ViewModel 中的代码)或将逻辑放入 ViewModel 中。如果我错了,请发布所有代码,以便我可以看到完整的图片。
  • Shoot me a reply - 这是通过使用 @EdPlunkett - 评论中带有 at 字符的名称来完成的。
  • 谢谢@Vojtěch Dohnal。
  • @Michael Puckett II 现在你可以看到全图了。

标签: c# wpf xaml viewmodel commandbinding


【解决方案1】:

看起来你从这里的深处钓到了一些奇怪的东西。但套用我的一个朋友的话,“好消息是,这次癌症更容易治疗了”。

首先,我使用PresentationTraceSources.TraceLevel 对您的绑定进行了活检:

<local:TestControl 
    LoadCommand="{Binding 
                    Source={StaticResource ViewModel}, 
                    Path=OpenCommand, 
                    PresentationTraceSources.TraceLevel=High}" 
    />

这就是我得到的:

System.Windows.Data Warning: 56 : Created BindingExpression (hash=34810426) for Binding (hash=11882558)
System.Windows.Data Warning: 58 :   Path: 'OpenCommand'
System.Windows.Data Warning: 60 : BindingExpression (hash=34810426): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=34810426): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=34810426): Attach to WpfApplication1.TestControl.LoadCommand (hash=5114324)
System.Windows.Data Warning: 67 : BindingExpression (hash=34810426): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=34810426): Found data context element: <null> (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=34810426): Activate with root item UserControlViewModel (hash=33108977)
System.Windows.Data Warning: 108 : BindingExpression (hash=34810426):   At level 0 - for UserControlViewModel.OpenCommand found accessor <null>
System.Windows.Data Error: 40 : BindingExpression path error: 'OpenCommand' property not found on 'object' ''UserControlViewModel' (HashCode=33108977)'. BindingExpression:Path=OpenCommand; DataItem='UserControlViewModel' (HashCode=33108977); target element is 'TestControl' (Name=''); target property is 'LoadCommand' (type 'ICommand')
System.Windows.Data Warning: 80 : BindingExpression (hash=34810426): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=34810426): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 89 : BindingExpression (hash=34810426): TransferValue - using final value <null>

这是发生了什么:在MainWindow.xaml 中,{StaticResource ViewModel} 正在TestControl 的上下文中被查找。效果和TestControl中调用FindResource一样:

public TestControl()
{
    InitializeComponent();

    var x = FindResource("ViewModel");

    //  Set breakpoint here and inspect x
    ;
}

TestControl 拥有自己的资源,其键为 ViewModel,因此查找找到了。该资源是一个UserControlViewModel,它没有OpenCommand 属性,在这种情况下,它隐藏MainWindow 中具有相同名称的完全不同的资源。

我不知道你从哪里得到这个视图模型资源方案,但你可以看到这是一个严重的错误。 MainWindow 中的任何人都不应该担心 TestControl 内部使用了哪些资源键。

幸运的是,没有必要制造这个问题。你可以去掉一大段代码,最终得到更简单、更健壮、更易于维护的东西。

所以,要解决这个问题:

首先,不要将所有视图模型都创建为资源,因为没有理由这样做并且会导致问题。将此ExecuteCommand 绑定与您拥有的绑定进行比较。你用这些东西得到了什么?没有。如果您不想设置 DataContext将其用作 DataContext

<UserControl.DataContext>
    <models:UserControlViewModel />
</UserControl.DataContext>
<Grid>
    <Button Content="_Load" IsDefault="True"
        Command="{Binding ExecuteCommand}"
        CommandParameter="{Binding Path=LoadCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TestControl}}}"/>
</Grid>

这就是MainWindow 的样子(省略你并不真正想要的CommandBindings):

<Window.DataContext>
    <models:MainWindowViewModel />
</Window.DataContext>

<StackPanel VerticalAlignment="Center">
    <!-- 
    local:TestControl.DataContext is its own viewmodel, so we use RelativeSource
    to get to the Window, and then we look at Window.DataContext for the main window
    viewmodel. 
    -->
    <local:TestControl 
        LoadCommand="{Binding DataContext.OpenCommand, RelativeSource={RelativeSource AncestorType=Window}}" 
        />
</StackPanel>

最后,对于 UserControls 创建自己的视图模型,这通常是不好的做法,原因现在必须非常明显。我发现当他们继承父母的DataContext 时,困惑要少得多。但是我已经把你的设计扔到窗外一天了,所以我们就不管它了。

【讨论】:

  • 感谢您的快速回答,但我更改了 Command 并没有帮助。也没有通过ViewModel“路由”命令,我不能添加某种自动行为,比如“真的覆盖这个文件?”东西。真正让我感到害怕的是,如果我只是将我的UserControl 扔到Window 上并使用预定义的Open 命令而不使用任何ViewModel 一切正常......但如果我试图让一切正确,添加一个@ 987654351@ 并尝试将 LoadCommand 绑定到我的 ViewModel 的命令我可以在 UserControl 后面的代码中看到 DependencyProperty 是空的。
  • 是的,我在UserControl 上定义了DependencyProperty,在ViewModel 上定义了DelegateCommand。我的UserControl 上的一个只是为了能够稍后在XAML 级别的Window 中添加Binding,并将Command Binding 传递给我声明它的ViewModel,以便添加一些监控和默认行为,例如“文件确实存在”的东西。
  • 再想一想,我想我可能猜错了一些你保密的代码。你在你的用户控件中设置DataContext = this; 吗?您需要共享用户控件的完整代码隐藏并显示视图模型的来源。实际上有一个名为ViewModelStaticResource 吗?如果有,在哪里?
  • @dukemadcat 我无法根据您告诉我的意图重构您的代码,当您可以复制和粘贴时尝试尝试是愚蠢的。 I need enough code to recreate the issue.
  • 我明天会分解所有内容并发布完整且运行的代码。
猜你喜欢
  • 1970-01-01
  • 2013-01-20
  • 2012-06-06
  • 2014-05-03
  • 2013-06-03
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多