我的 WPF 自定義控件的數據上下文正在取代父級的 (My WPF custom control's Data Context is superseding parent's)


問題描述

我的 WPF 自定義控件的數據上下文正在取代父級的 (My WPF custom control's Data Context is superseding parent's)

In my main window, I try to bind to a bool, but it's looking in my custom control's DataContext instead. If I don't assign DataContext in the user control, then the main window's bindings works, but (obviously) this brakes the bindings in the user control.

Here's the error:

  

System.Windows.Data Error: 40 : BindingExpression path error: 'MyControlVisible' property not found on 'object' ''MyUserControlModel' (HashCode=1453241)'. BindingExpression:Path=MyControlVisible; DataItem='MyUserControlModel' (HashCode=1453241); target element is 'MyUserControl' (Name='_myUserControl'); target property is 'Visibility' (type 'Visibility')

I need binding to work on both controls, but I don't want the user control's DataContext to supersede the window's.

Here's the code:

<Window x:Class="Sandbox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Controls="clr‑namespace:Sandbox.Controls" Title="Sandbox">
    <DockPanel LastChildFill="True">
        <DockPanel.Resources>
            <BooleanToVisibilityConverter x:Key="boolToVis" />
        </DockPanel.Resources>
        <Grid>
            <Controls:MyUserControl x:Name="_myUserControl" Visibility="{Binding MyControlVisible, Converter={StaticResource boolToVis}}"/>
        </Grid>
    </DockPanel>
</Window>

namespace Sandbox
{
    public partial class MainWindow
    {
        private MainWindowModel model;
        public MainWindow()
        {
            InitializeComponent();
            DataContext = model = new MainWindowModel();
            _myUserControl.Initialize(model.MyUControlModel);
        }
    }
}

using System.ComponentModel;
using Sandbox.Controls;

namespace Sandbox
{
    public class MainWindowModel : BaseModel
    {
        public MyUserControlModel MyUControlModel { get; set; }
        public bool MyControlVisible { get; set; }
        public MainWindowModel()
        {
            MyUControlModel = new MyUserControlModel();
            MyControlVisible = false;
            OnChange("");
        }
    }

    public class BaseModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnChange(string s)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(s));
            }
        }
    }
}

<UserControl x:Class="Sandbox.Controls.MyUserControl"
             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" 
             mc:Ignorable="d">
    <Grid>
        <TextBlock Text="{Binding MyBoundText}"/>
    </Grid>
</UserControl>

namespace Sandbox.Controls
{
    public partial class MyUserControl
    {
        public MyUserControl()
        {
            InitializeComponent();
        }

        public void Initialize(MyUserControlModel context)
        {
            DataContext = context;
        }
    }

}

namespace Sandbox.Controls
{
    public class MyUserControlModel : BaseModel
    {
        public string MyBoundText { get; set; }
        public MyUserControlModel()
        {
            MyBoundText = "Hello World!";
            OnChange("");
        }
    }
}

參考解法

方法 1:

That is one of the many reasons you should never set the DataContext directly from the UserControl itself. 

When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded in. 

In the case of your binding, normally the DataContext would be inherited so the Visibility binding could find the property MyControlVisible on the current DataContext, however because you hardcoded the DataContext in your UserControl's constructor, that property is not found.

You could specify a different binding source in your binding, such as 

<Controls:MyUserControl Visibility="{Binding 
    RelativeSource={RelativeSource AncestorType={x:Type Window}}, 
    Path=DataContext.MyControlVisible, 
    Converter={StaticResource boolToVis}}" ... />

However that's just a workaround for the problem for this specific case, and in my view is not a permanent solution. A better solution is to simply not hardcode the DataContext in your UserControl

There are a few different ways you can do depending on your UserControl's purpose and how your application is designed.

  • You could create a DependencyProperty on your UserControl to pass in the value, and bind to that.

    <Controls:MyUserControl UcModel="{Binding MyUControlModelProperty}" ... />
    

    and

    <UserControl x:Class="Sandbox.Controls.MyUserControl"
                 ElementName=MyUserControl...>
        <Grid DataContext="{Binding UCModel, ElementName=MyUserControl}">
            <TextBlock Text="{Binding MyBoundText}"/>
        </Grid>
    </UserControl>
    
  • Or you could build your UserControl with the expectation that a specific property will get passed to it in the DataContext. This is normally what I do, in combination with DataTemplates.

    <Controls:MyUserControl DataContext="{Binding MyUControlModelProperty}" ... />
    

    and

    <UserControl x:Class="Sandbox.Controls.MyUserControl"...>
        <Grid>
            <TextBlock Text="{Binding MyBoundText}"/>
        </Grid>
    </UserControl>
    
  • As I said above, I like to use DataTemplates to display my UserControls that expect a specific type of Model for their DataContext, so typically my XAML for the main window would look something like this:

    <DataTemplate DataType="{x:Type local:MyUControlModel}">
        <Controls:MyUserControl />
    </DataTemplate>
    
    <ContentPresenter Content="{Binding MyUControlModelProperty}" ... />
    

(by user2370851Rachel)

參考文件

  1. My WPF custom control's Data Context is superseding parent's (CC BY‑SA 3.0/4.0)

#wpf #binding #data-binding #datacontext #C#






相關問題

usercontrol wpf 靜態內存 (usercontrol wpf static memory)

將圖像加載到面板 (Load image to panel)

我的 WPF 自定義控件的數據上下文正在取代父級的 (My WPF custom control's Data Context is superseding parent's)

是否可以更改 WPF 控件的父級 (Is it possible to change parent of WPF control)

WPF MVVM 鏈接視圖 (WPF MVVM Linked Views)

wpf 樹視圖 mvvm (wpf treeview mvvm)

應用啟動後更改應用圖標 (Change application icon after app launched)

在VB中過濾一個wpf collectionviewsource? (Filter a wpf collectionviewsource in VB?)

如何使 DockPanel 中的項目擴展以適應 WPF 中的所有可用空間? (How to make items in a DockPanel expand to fit all available space in WPF?)

使用 IsCheckable="True" 綁定 MenuItem 中的鍵時出現問題,為什麼? (Problems binding keys in MenuItem's with IsCheckable="True", why?)

如何將文本框的borderBrush屬性綁定到viewmodel中的屬性,類型轉換錯誤 (How to bind the borderBrush property of a textbox to a property in viewmodel, type conversion error)

Painter 應用程序的自定義控件以顯示不同的橡皮擦 (Custom Control for Painter application to show different erasers)







留言討論