WPF C#UserControl DependencyProperty命令绑定为空

时间:2017-02-27 15:08:52

标签: c# wpf xaml viewmodel commandbinding

我创建了一个UserControl作为文件浏览工具,我希望将Commands实现为DependencyProperties以便加载和保存,以便我可以绑定Commands来自ViewModel 1}}以便处理它们。

现在的问题是,如果我使用预定义的Commands OpenSave并在我的Window处理它,那么它可以使用{{1}来自我的Bindings这些ViewModelCommands ...

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

用户控件

XAML

null

代码隐藏

<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();
        }
    }
}

窗口

XAML

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);
        }
    }
}

代码隐藏

<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没有。

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

1 个答案:

答案 0 :(得分:0)

看起来你在这里挖出了奇怪的东西。但是要解释我的一个朋友,&#34;好消息是,这次癌症更容易治疗&#34;。

首先,我使用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的上下文中查找FindResource。效果与在TestControl中调用public TestControl() { InitializeComponent(); var x = FindResource("ViewModel"); // Set breakpoint here and inspect x ; }

相同
TestControl

ViewModel拥有自己的资源UserControlViewModel,以便查找找到的内容。该资源是OpenCommand,没有MainWindow属性,在这种情况下,隐藏完全不同的资源MainWindow中的同名资源。

我不知道你从哪里获得这个viewmodel资源方案,但你可以看到它是一个严重的错误。 TestControl中的任何人都不必担心ExecuteCommand内部使用的资源键。

幸运的是,没有必要创建该问题。你可以剔除一大堆代码,最终得到更简单,更健壮,更易于维护的东西。

所以,解决问题:

首先,不要将所有视图模型创建为资源,因为没有理由这样做会导致问题。将此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> 将其用作DataContext

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> 事物):

DataContext

最后,由于一些现在必须显而易见的原因,UserControls创建自己的视图模型,这通常是不好的做法。我发现当他们继承父母的.toggle()时,他们的困惑就更少了。但是我已经把你的设计抛到窗外一天了,所以我们将把它留下来。