wpf c# 数据绑定以使用 viewModel 对象的属性设置字符串

wpf c# data binding to set string using property of viewModel object

我花了好几个小时试图解决这个问题:

我有名为 NewMazeGrid 的用户自定义网格控件,我想将其用作 MainWindow 中的控件。 MainWindow 包含 MazeViewModel(mazeVM 成员)。

当属性 MazeViewModel:MySingleplay 更改时,我正在尝试设置网格的值。
(我正在为它使用 INotifyPropertyChanged,它工作得很好。我猜,问题出在最终绑定中)
代码:

这是属性 MazeViewModel:MySingleplay getter:

1
2
3
4
5
6
7
8
9
10
11
12
13
public string MySingleplay
        {
            get
            {
                if (myModel.MySingleplay == null)
                {
                    return"";
                } else
                {
                    return myModel.MySingleplay.ToString();//works perfect
                }
            }
        }

这是NewMazeGrid.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace VisualClient.View.controls
{
    public partial class NewMazeGrid : UserControl
    {
        private MazePresentation myMaze;
        private string order; //dont really use it

        //Register Dependency Property

        public static readonly DependencyProperty orderDependency =
            DependencyProperty.Register("Order", typeof(string), typeof(NewMazeGrid));

        public NewMazeGrid()
        {
            myMaze = new MazePresentation();
            InitializeComponent();
            DataContext = this;
            lst.ItemsSource = myMaze.MazePuzzleLists;
        }

        public string Order
        {
            get
            {
                return (string)GetValue(orderDependency);
            }
            set
            {
                SetValue(orderDependency, value);
                myMaze.setPresentation(value); //(parsing string into matrix)
            }
        }
    }
}

这是MainWindow.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial class MainWindow : Window
    {
        private MazeViewModel mazeVM;

        public MainWindow()
        {
            InitializeComponent();

            mazeVM = new MazeViewModel(new ClientMazeModel(new TCPClientConnection()));
            DataContext = mazeVM;

            mazeVM.connectToServer();
        }

        private void bu_Click(object sender, RoutedEventArgs e)
        {
            bool isC = mazeVM.isConnected();
            mazeVM.openSingleplayGame("NewMaze");//works perfect
        }

这是MainWindow.xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Window x:Class="VisualClient.View.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:Controls ="clr-namespace:VisualClient.View.controls"
        xmlns:vm ="clr-namespace:VisualClient.ViewModel"
        xmlns:local="clr-namespace:VisualClient.View"

        mc:Ignorable="d"
        Title="Main Window" Height="350" Width="525" MinWidth="900" MinHeight="600">
    <WrapPanel >
        <Button Name ="bu" Content="Click_Me" Click="bu_Click"/>
        <Grid Name="myGrid">
            <Controls:NewMazeGrid Order="{Binding MySingleplay, UpdateSourceTrigger=PropertyChanged}"/>
        </Grid>
    </WrapPanel>
</Window>

我在绑定行收到此错误:值不能为空。

总结:
它在 ctor 中很好地初始化窗口,但是当属性更改时,它不会进入 Order 属性设置器。因此我的网格永远不会改变。

在这种情况下,绑定的正确语法应该是什么?我如何将它绑定到正确的属性?

文件夹层次结构浏览器


WPF 可能不会调用依赖属性的 CLR package器,而只是直接调用底层 DependencyObject 的 GetValueSetValue 方法。这就是为什么除了 GetValue 和 SetValue 调用之外不应该有任何逻辑。

这在 XAML 加载和依赖属性中进行了解释:

Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.

Similarly, other aspects of the XAML processor that obtain property
values from XAML processing also use GetValue rather than using the
wrapper. Therefore, you should also avoid any additional
implementation in the get definition beyond the GetValue call.

要获得有关属性值更改的通知,您可以通过属性元数据注册 PropertyChangedCallback。另请注意,DependencyProperty 字段有一个命名约定。你的应该被称为 OrderProperty:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static readonly DependencyProperty OrderProperty =
    DependencyProperty.Register(
       "Order", typeof(string), typeof(NewMazeGrid),
        new PropertyMetadata(OnOrderChanged));

public string Order
{
    get { return (string)GetValue(OrderProperty); }
    set { SetValue(OrderProperty, value); }
}        

private static void OnOrderChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    ((NewMazeGrid)obj).myMaze.setPresentation((string)e.NewValue);
}

除此之外,您不得设置

1
DataContext = this;

NewMazeGrid 的构造函数中。这有效地防止了从父窗口继承 DataContext,因此 {Binding MySingleplay} 将不起作用。除非在特殊情况下,您不应显式设置 UserControl 的 DataContext.

因此,从构造函数中删除 DataContext 赋值:

1
2
3
4
5
6
public NewMazeGrid()
{
    myMaze = new MazePresentation();
    InitializeComponent();
    lst.ItemsSource = myMaze.MazePuzzleLists;
}

也就是说,也不需要在单向绑定上设置 UpdateSourceTrigger=PropertyChanged。它只对双向(或单向源)绑定有效:

1
<Controls:NewMazeGrid Order="{Binding MySingleplay}"/>