WPF - 如何从 DataTemplate 为自定义用户控件设置正确的 DataContext

问题描述 投票:0回答:1

我正在尝试制作一个自定义用户控件(

TSFloorPlanProductDispenserStockViewer
)来显示错误代码和供用户交互的按钮列表。在 DataTemplate (
TSFloorPlanInputStationProductDispenserMemberTemplate
) 中,我希望使用此自定义 UserControl 的两个“实例”,但我弄乱了 DataContext,但我不确定如何正确设置它。

TSFloorPlanProductDispenserStockViewer.xaml

<UserControl x:Class="ASC_Control_HMI.TSFloorPlanProductDispenserStockViewer"
             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:ASC_Control_HMI"
             mc:Ignorable="d"
             Name="ucProductDispenserStockViewer"
             >

    <Grid>
        <DockPanel>
            <Border DockPanel.Dock="Top" Background="LightYellow">
                <TextBlock x:Name="tbProductDispenserStockID" FontSize="20" HorizontalAlignment="Center"/>
            </Border>
            <Border x:Name="brdChangeButtons" Background="LightSalmon" DockPanel.Dock="Bottom" Margin="4,0,4,0" Height="60" BorderBrush="White" CornerRadius="4" BorderThickness="1">
                <local:TSRemoteManualControlBar x:Name="DispenserStockManualControlBar" HorizontalAlignment="Center" ManualControlBarActions="{Binding ViewerManualControlBarActions}">

                </local:TSRemoteManualControlBar>
            </Border>
            <Border DockPanel.Dock="Bottom" Background="LightBlue">
                <TextBlock x:Name="tbErrorStock" FontSize="16" HorizontalAlignment="Center" Text="{Binding ProductDispenserStockError}"/>
            </Border>
        </DockPanel>
    </Grid>
</UserControl>

TSFloorPlanProductDispenserStockViewer.xaml.cs
(摘录):

public static readonly DependencyProperty ProductDispenserStockErrorProperty = DependencyProperty.Register(
    "ProductDispenserStockError", typeof(string), typeof(TSFloorPlanProductDispenserStockViewer),
    new PropertyMetadata(null, new PropertyChangedCallback(TSFloorPlanProductDispenserStockViewer.OnProductDispenserStockErrorPropertyChanged)));

private static void OnProductDispenserStockErrorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    TSFloorPlanProductDispenserStockViewer control = d as TSFloorPlanProductDispenserStockViewer;
    if ((control != null) && (e.NewValue != null))
    {
        //(control.DataContext as TSFloorPlanProductDispenserStockViewer).ProductDispenserStockError = Convert.ToString(e.NewValue);
        control.DataContext = e.NewValue;
    }
}

public string ProductDispenserStockError
{
    get
    {
        return (string)GetValue(ProductDispenserStockErrorProperty);
    }
    set 
    {
        SetValue(ProductDispenserStockErrorProperty, value);
    }
}

public static readonly DependencyProperty ViewerManualControlBarActionsProperty = DependencyProperty.Register(
    "ViewerManualControlBarActions", typeof(ObservableCollection<TSRemoteManualControlBarAction>), typeof(TSFloorPlanProductDispenserStockViewer),
    new PropertyMetadata(new PropertyChangedCallback(TSFloorPlanProductDispenserStockViewer.OnManualControlBarActionsPropertyChanged)));

private static void OnManualControlBarActionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    TSFloorPlanProductDispenserStockViewer control = d as TSFloorPlanProductDispenserStockViewer;

    if ((control != null) && (e.NewValue != null))
    {
        control.DataContext = e.NewValue;
    }
}

private ObservableCollection<TSRemoteManualControlBarAction> m_ViewerManualControlBarActions;
public ObservableCollection<TSRemoteManualControlBarAction> ViewerManualControlBarActions
{
    get
    {
        return (ObservableCollection<TSRemoteManualControlBarAction>)GetValue(ViewerManualControlBarActionsProperty);
    }
    set
    {
        SetValue(ViewerManualControlBarActionsProperty, value);
    }
}

TSFloorPlanInputStationProductDispenserMemberTemplate.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ASC_Control_HMI"
                    >
    
    <local:BoolToOpacityValueConverter x:Key="IsActiveToOpacity"/>
    <local:BoolToVisibilityValueConverter x:Key="IsActiveToVisibility"/>

    <DataTemplate x:Key="FloorPlanInputStationProductDispenserMemberTemplate" DataType="{x:Type local:TSFloorPlanInputStationProductDispenser}">
        <Grid Visibility="{Binding IsVisible, Mode=OneWay, Converter={StaticResource IsActiveToVisibility}}">
            <DockPanel>
                <DockPanel DockPanel.Dock="Bottom">
                    <Grid x:Name="grdDispenserStockViewers" Margin="0,10,0,0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="60"/>
                            <!--<RowDefinition Height="*"/>-->
                        </Grid.RowDefinitions>

                        <local:TSFloorPlanProductDispenserStockViewer Grid.Row="0" Grid.Column="0" x:Name="ProductDispenserStockViewer1" ProductDispenserStockID="Upper" ProductDispenserStockError="{Binding UpperStockError}" ViewerManualControlBarActions="{Binding ManualControlBarActions1}">

                        </local:TSFloorPlanProductDispenserStockViewer>
                        <local:TSFloorPlanProductDispenserStockViewer Grid.Row="0" Grid.Column="1" x:Name="ProductDispenserStockViewer2" ProductDispenserStockID="Lower" ProductDispenserStockError="{Binding LowerStockError}" ViewerManualControlBarActions="{Binding ManualControlBarActions2}">

                        </local:TSFloorPlanProductDispenserStockViewer>
                    </Grid>
                </DockPanel>
            </DockPanel>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

我在

TSFloorPlanProductDispenserStockViewer
的构造函数中将错误字符串设置为“DefaultError”,以便至少看到一些内容,但它们打印在为
ManualControlBar
保留的插槽中。我很确定我的 DataContext 设置是错误的,但我无法通过我发现的有关数据绑定的其他帖子来正确设置它。

任何帮助将不胜感激

=======编辑=======

我已经按照 @bioniccode 的建议删除了查看器对 DataContext 的引用,并且我为查看器属性

ProductDispenserStockError
ViewerManualControlBarActions
提供的任何默认值都会显示。当我尝试使用 UserControl 的父级属性作为这些值时,它们不会更新

<local:TSFloorPlanProductDispenserStockViewer Grid.Row="0" Grid.Column="0"
    x:Name="ProductDispenserStockViewer1"
    ProductDispenserStockID="Upper"
    ProductDispenserStockError="{Binding UpperStockError}"
    ViewerManualControlBarActions="{Binding ManualControlBarActions1}">

</local:TSFloorPlanProductDispenserStockViewer>
<local:TSFloorPlanProductDispenserStockViewer Grid.Row="0" Grid.Column="1"
    x:Name="ProductDispenserStockViewer2"
    ProductDispenserStockID="Lower"
    ProductDispenserStockError="{Binding LowerStockError}"
    ViewerManualControlBarActions="{Binding ManualControlBarActions2}">

如何将查看器属性绑定到模板“代码隐藏”中的属性?

wpf data-binding datatemplate
1个回答
-1
投票

您没有在内部设置 DataContext。你永远不会这样做。你甚至没有得到

DataContext
。您的控件无需知道
DataContext
即可进行操作。

内部元素始终绑定到自定义控件(或

UserControl
)的依赖属性。创建
ControlTemplate
时,您可以使用
{TemplateBinding}
{Binding RelativeSource={realtiveSource TemplatedParent}}
进行绑定。在所有其他情况下,您都使用
Binding.ElementName
Binding.RelativeSource

例如

TSFloorPlanProductDispenserStockViewer.xaml.cs
删除对 DataContext 的所有引用。

public static readonly DependencyProperty ProductDispenserStockErrorProperty = DependencyProperty.Register(
  "ProductDispenserStockError", 
  typeof(string), 
  typeof(TSFloorPlanProductDispenserStockViewer));

public string ProductDispenserStockError
{
  get => (string)GetValue(ProductDispenserStockErrorProperty);
  set => SetValue(ProductDispenserStockErrorProperty, value);
}

public static readonly DependencyProperty ViewerManualControlBarActionsProperty = DependencyProperty.Register(
  "ViewerManualControlBarActions", 
  typeof(ObservableCollection<TSRemoteManualControlBarAction>), 
  typeof(TSFloorPlanProductDispenserStockViewer));

public ObservableCollection<TSRemoteManualControlBarAction> ViewerManualControlBarActions
{
  get => (ObservableCollection<TSRemoteManualControlBarAction>)GetValue(ViewerManualControlBarActionsProperty);
  set => SetValue(ViewerManualControlBarActionsProperty, value);
}

TSFloorPlanProductDispenserStockViewer.xaml.cs
然后修复绑定并直接绑定到由

UserControl
定义的依赖属性。

<UserControl x:Name="Root">
  <Grid>
    <DockPanel>
 
      <!-- Option #1: Binding.ElementName (recommended) -->
      <local:TSRemoteManualControlBar x:Name="DispenserStockManualControlBar" 
                                      ManualControlBarActions="{Binding ElementName=Root, 
                                                                        Path=ViewerManualControlBarActions}" />

      <!-- Option #2: Binding.RelativeSource -->
      <TextBlock x:Name="tbErrorStock" 
                 Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, 
                                Path=ProductDispenserStockError}"/>
    </DockPanel>
  </Grid>
</UserControl>

现在您可以照常使用该控件。外部 DataContext 值不再被覆盖,因此控件上设置的外部绑定可以正常运行。

© www.soinside.com 2019 - 2024. All rights reserved.