【问题标题】:Dynamically use different UserControl based on datacontext基于datacontext动态使用不同的UserControl
【发布时间】:2017-09-08 17:49:54
【问题描述】:

我有一块 xaml 将相同的模式复制了六次,我希望通过消除重复来减少占用空间。我遇到了一个需要帮助的问题。

背景:我有一个类实例化另一个类六次(项目的各个阶段,如您所愿)。

   public ECN(string ecnNumber) {
        _ECNNumber = ecnNumber;

        //instantiate each phase to be populated or not
        _PurchaseParts = new ECNPhase();
        _PieceParts = new ECNPhase();
        _Weldments = new ECNPhase();
        _BOMCfg = new ECNPhase();
        _Cleanup = new ECNPhase();
        _PRAF = new ECNPhase();   
    }

在每个阶段内部是 ECNPhase 类中引用的变更(另一个类)的集合。每个阶段都有独特的数据,每个阶段都显示在一个独特的视图中,这就是我的障碍所在,稍后我将展示。

重复的 xaml 代码示例,主要区别在于每个扩展器内的不同视图:

<StackPanel Margin="0">

    <!--Section for Purchase parts-->
    <StackPanel Orientation="Horizontal" >
        <CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase,Mode=TwoWay}"/>
        <StackPanel Orientation="Horizontal">
            <StackPanel.Style>
                <Style TargetType="{x:Type StackPanel}">
                    <Setter Property="IsEnabled" Value="False"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase}" Value="True">
                            <Setter Property="IsEnabled" Value="True"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </StackPanel.Style>
            <Expander Header="Purchase Parts" Margin="0,0,10,0" Width="110">
                <view:PurchasePartsView/>
            </Expander>
            <CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
            <Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
            <Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.Status}"/>
        </StackPanel>
    </StackPanel>

    <!--Section for Piece Parts-->
    <StackPanel Orientation="Horizontal">
        <CheckBox  Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase,Mode=TwoWay}"/>
        <StackPanel Orientation="Horizontal">
            <StackPanel.Style>
                <Style TargetType="{x:Type StackPanel}">
                    <Setter Property="IsEnabled" Value="False"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase}" Value="True">
                            <Setter Property="IsEnabled" Value="True"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </StackPanel.Style>
            <Expander Header="Piece Parts"  Margin="0,0,10,0" Width="110">
                <view:PiecePartsView/>
            </Expander>
            <CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
            <Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
            <Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.Status}"/>
        </StackPanel>
    </StackPanel>
    <!--duplicated four more times-->
</StackPanel>

我想做的是:

<StackPanel>
    <view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
    <view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
    <!--four more phases-->
</StackPanel>

PhaseView 将是处理复制的模板,这就是我遇到的问题。每个阶段都需要根据 PhaseView 的数据上下文选择一个唯一的视图 (userControl)。

<StackPanel Orientation="Horizontal" >
    <CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=HasPhase,Mode=TwoWay}"/>
    <StackPanel Orientation="Horizontal">
        <StackPanel.Style>
            <Style TargetType="{x:Type StackPanel}">
                <Setter Property="IsEnabled" Value="False"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=HasPhase}" Value="True">
                        <Setter Property="IsEnabled" Value="True"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </StackPanel.Style>
        <Expander Header="DisplayName" Margin="0,0,10,0" Width="110">
            <!--add somthing here to select the correct view based on the datacontext-->
            <!--<local:PurchasePartsView/>  This user control adds a datagrid that is unique to each phase-->
        </Expander>
        <CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
        <Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
        <Label Content="{Binding Path=Status}"/>
    </StackPanel>
</StackPanel>

我正在考虑以某种方式使用如下所示的数据触发器,但我没有任何运气弄明白。我知道必须有一种方法可以做到这一点,而且这可能是一些简单而愚蠢的事情。任何帮助将不胜感激。

<DataTrigger Binding="{Binding Path=DisplayName}" Value="Purchase Parts">
   <Setter Property="DataContext" Value="{Binding }"/> <!--Don't know how to bind the DataContext-->
</DataTrigger>

谢谢,

【问题讨论】:

  • 有 3 个选项。创建一个DataTemplateSelector 以编程方式执行此操作,在Resources 中创建没有KeyDataTemplates,或使用Triggers。我个人的偏好是将每种类型的DataTemplate 放入Resources,然后让 WPF 自动选择正确的类型。这是Example
  • 我唯一的问题是这个类被实例化了好几次。因此,如果我使用DataTemplate,它看起来像DataType="Local:ECNPhase",这无助于我选择同一类的不同实例。
  • DataTemplateSelector 可能就是你想要的。它具有最大的灵活性。它将让您不仅可以通过Type 选择Template,还可以通过当前项目的DataContext 实例中包含的数据来选择Template
  • This 是另一本关于根据DataContext 中的数据更改DataTemplate 的好书。

标签: c# wpf xaml mvvm


【解决方案1】:

好的,感谢 Bradley,我查看了 DataTemplateSelector,这就是我想出的。

在我的 UserControl 资源中,我设置了几个 DataTemplate 和对覆盖 DataTemplateSelector 类的 TemplateSelector 类的引用。

XAML 资源:

<UserControl.Resources>

    <local:TemplateSelector x:Key="myTemplateSelector"/>

    <DataTemplate x:Key="PurchasePartsTemplate">
        <view:PurchasePartsView/>
    </DataTemplate>
    <DataTemplate x:Key="PiecePartsTemplate">
        <view:PiecePartsView/>
    </DataTemplate>
    <!--Four more templates-->        
</UserControl.Resources>

DataTemplateSelector 覆盖的代码隐藏。注意:我无法找到绑定到 ECNPhase 类的方法,所以我绑定到我的类中的 DisplayName 属性以提取正确的实例。

class TemplateSelector : DataTemplateSelector {

    public override DataTemplate SelectTemplate(object item, DependencyObject container) {

        FrameworkElement element = container as FrameworkElement;

        if(element != null && item != null && item is string) {
            string phase = (string)item;

            if(phase == "Purchase Parts") {
                return element.FindResource("PurchasePartsTemplate") as DataTemplate;
            }else if(phase == "Piece Parts") {
                return element.FindResource("PiecePartsTemplate") as DataTemplate;
            }
        }
        return null;
    }
}

我在我的 UserContol 中这样调用这个类:

<Expander Header="{Binding Path=DisplayName}" Margin="0,0,10,0" Content="{Binding Path=DisplayName}" 
                  ContentTemplateSelector="{StaticResource myTemplateSelector}"/>

没有与扩展器关联的项目控件,因此我使用了内容控件。我将 DisplayName 传递给控件属性,并且 contentTemplateSelector 使用 myTemplateSelector 资源,该资源进入代码隐藏并根据 DisplayName 选择要使用的适当数据模板。

现在我可以像这样调用我的可重用模板:

<StackPanel Margin="0">
    <view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
    <view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
</StackPanel>

@Bradley,感谢您为我指明了正确的方向。

【讨论】:

    猜你喜欢
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    相关资源
    最近更新 更多