WPF 自定义控件选取父窗口的数据上下文

本文关键字:数据 上下文 窗口 自定义控件 选取 WPF | 更新日期: 2023-09-27 18:32:53

我有一个视图模型

 public class ViewModel:ViewModelObject
{
    public ViewModel()
    {
        ProjectionDataElementList = new ObservableCollection<ProjectionDataElement>();
    }
    public ObservableCollection<ProjectionDataElement> ProjectionDataElementList { get; set; }
    private ProjectionDataElement _currentSelectedProjectionDataElement;
    public ProjectionDataElement CurrentSelectedProjectionDataElement
    {
        get 
        { 
            return _currentSelectedProjectionDataElement; 
        }
        set 
        { 
            _currentSelectedProjectionDataElement = value;
            OnPropertyChanged("CurrentSelectedProjectionDataElement");
        }
    }
}

名为 ProjectorDisplayControl 的控件

<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
         ..................>
<Viewbox>
    <StackPanel>
        <TextBlock Text="{Binding Path=TextBody}"/>
    </StackPanel>
</Viewbox>

    public partial class ProjectorDisplayControl : UserControl
{
    public ProjectorDisplayControl()
    {
        InitializeComponent();
    }
    public static readonly DependencyProperty ProjectedDataProperty = DependencyProperty.Register("ProjectedData", typeof(ProjectionDataElement), typeof(ProjectorDisplayControl),
            new PropertyMetadata(new PropertyChangedCallback((objectInstance, arguments) =>
            {
                ProjectorDisplayControl projectorDisplayControl = (ProjectorDisplayControl)objectInstance;
                projectorDisplayControl._projectedData = (ProjectionDataElement)arguments.NewValue;
            })));
    public ProjectionDataElement ProjectedData
    {
        get
        {
            return (ProjectionDataElement)GetValue(ProjectedDataProperty);
        }
        set
        {
            SetValue(ProjectedDataProperty, value);
        }
    }

    private ProjectionDataElement _projectedData
    {
        get
        {
            return this.DataContext as ProjectionDataElement;
        }
        set
        {
            this.DataContext = value;
        }
    }
}

该控件在两个位置使用。

第一名是在列表框中,它工作得很好:

<ListBox Grid.Column="0" ItemsSource="{Binding Path=ProjectionDataElementList}" Name="projectedDataListBox" 
             SelectedItem="{Binding Path=CurrentSelectedProjectionDataElement, UpdateSourceTrigger=PropertyChanged}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <Border BorderThickness="1" BorderBrush="Black">
                        <controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding}"/>
                    </Border>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

第二位是在窗体的顶部,我在其中使用 ViewModel 对象设置窗体的 DataContext。为了使ProjectorDisplayControl使用ViewModel中的CurrentSelectedProjectionDataElement,我希望必须这样做:

<Window x:Class="Fast_Project.DisplayWindow"
    ................>
<Viewbox>
    <StackPanel>
        <controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding CurrentSelectedProjectionDataElement}"/>
    </StackPanel>
</Viewbox>

该代码给了我两个绑定错误:

系统.视窗.数据错误: 40 : 绑定表达式路径错误: 在"对象"视图模型"(HashCode=2512406("上找不到"TextBody"属性。 BindingExpression:Path=TextBody; DataItem='ViewModel' (HashCode=2512406(; 目标元素是"文本块"(名称="(; 目标属性为"文本"(类型"字符串"(

系统.视窗.数据错误: 40 : 绑定表达式路径错误: 在"对象"投影数据元素"(HashCode=37561097("上找不到"当前选定投影数据元素"属性。 BindingExpression:Path=CurrentSelectedProjectionDataElement; DataItem='ProjectionDataElement' (HashCode=37561097(; 目标元素是"投影或显示控件"(名称="_projectorDisplay"(; 目标属性是"投影数据"(类型"投影数据元素"(

当我在 ProjectorDisplayControl 上观看私有属性_projectedData设置数据上下文的设置器时,我首先看到它被设置为有效值,然后设置为 null。

在保存单个 ProjectorDisplayControl 的 DisplayWindow 中,我可以删除与 CurrentSelectedProjectionDataElement 的绑定,然后我只收到第一个绑定错误消息:

<Viewbox>
    <StackPanel>
        <controls:ProjectorDisplayControl x:Name="_projectorDisplay" />
    </StackPanel>
</Viewbox>

第一个绑定错误让我觉得当设置DisplayWindow的DataContext时,ProjectorDisplayControl的DataContext正在设置ViewModel对象。但据我所知,控件不会与其父窗口共享相同的数据上下文,除非您这样设置。

我已经将 DisplayWindow 中的 ProjectorDisplayControl.ProjectedData 的绑定路径视为第二个错误消息状态的 ProjectionDataElement 对象。

<Viewbox>
    <StackPanel>
        <controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding }"/>
    </StackPanel>
</Viewbox>

然后是告诉我:

无法创建默认转换器以在类型"Fast_Project.ViewModel"和"Fast_Project.ProjectionDataElement"之间执行"单向"转换

就像它真的是 ViewModel 对象一样,就像我最初认为的那样......

无论如何,我怀疑我问题的根源在于我如何看到ProjectorDisplayControl将DataContext设置为ViewModel对象。有人看到我在哪里搞砸了吗?

谢谢大家的帮助!


解决方案是:


投影仪显示控制

        <StackPanel>
        <TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:ProjectorDisplayControl}}}"/>
    </StackPanel>

显示窗口

<StackPanel>
        <controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding Path=ViewModel.CurrentSelectedProjectionDataElement, 
            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:DisplayWindow}}}"/>
    </StackPanel>

WPF 自定义控件选取父窗口的数据上下文

子控件从其父控件继承依赖项属性值(在本例中为 DataContext(。当您在 itemTempalte 中使用 UserControl 时,每个项的 DataContext 已经是 ProjectionDataElement,因此控件的 DataContext 设置为 ProjectionDataElement。

当您使用父级中的控件时,它将继承其数据上下文。

问题是您在控件上设置 ProjectedData 属性,而不是在其中使用它。如果希望每个控件都应绑定到 ProjectedData 上设置的值,则应更新绑定,如下所示:

<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
         ..................>
<Viewbox>
    <StackPanel>
        <TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Self}"/>
    </StackPanel>

对于第二个错误,您必须将该窗口的DataContext设置为投影数据元素,这就是在其中搜索它的原因。