【问题标题】:WPF: How to make styles in XAML conditional on a variable in your own C# codeWPF:如何使 XAML 中的样式以您自己的 C# 代码中的变量为条件
【发布时间】:2015-07-24 16:58:10
【问题描述】:

WPF 和 XAML 新手在这里....

我需要将 XAML 代码中的 WPF TriggerDataTrigger 绑定到 XAML 控件类以外的类中的某些 C# 代码中。这非常令人沮丧,因为我读过的所有 28,000 篇教程都只给出了 TriggerDataTrigger 的一个简单示例,其中涉及 已经存在的属性(例如 MouseOver),没有一个给出示例如何将它与您自己的 C# 代码联系起来。

我有一个显示各种报告类型的屏幕。所有报告类型的 XAML 都是相同的,除了诊断报告,我的要求是 DataGrid 单元格配置为TextBlock.TextAlignment="Left",而所有其他报告(即默认值)应为TextBlock.TextAlignment="Center"。 (还有一些其他差异;为简洁起见,我只想说这是唯一的差异。)我真的不想复制整个 XAML 来特殊情况下的诊断报告,因为其中 99% 是与其他报告相同。

要使用触发器,我想也许我需要我的类从 DependencyObject 继承,以便我可以在其中定义 DependencyProperty(作为 WPF 新手,我意识到我可能会说一些非常奇怪的事情)。所以在我的 C# 代码中,我有一个类...

namespace MyApplication
{
   public enum SelectedReportType
   {
      EquipSummary,
      EventSummary,
      UserSummary,
      DiagSummary
   }

   public sealed class ReportSettingsData : DependencyObject
   {
      private static ReportSettingsData _instance; // singleton

      static ReportSettingsData() { new ReportSettingsData(); }

      private ReportSettingsData() // private because it's a singleton
      {
         if (_instance == null) // only true when called via the static constructor
            _instance = this; // set here instead of the static constructor so it's available immediately
         SelectedReport = SelectedReportType.EquipSummary; // set the initial/default report type
      }

      public static ReportSettingsData Instance
      {
         get { return _instance; }
      }

      public static SelectedReportType SelectedReport
      {
         get { return (SelectedReportType)Instance.GetValue(SelectedReportProperty); }
         set { Instance.SetValue(SelectedReportProperty, value); }
      }

      public static readonly DependencyProperty SelectedReportProperty =
         DependencyProperty.Register("SelectedReport", typeof(SelectedReportType), typeof(ReportSettingsData));
   }
}

所以在我的 XAML 文件中,我使用了 TriggerDataTrigger 的各种咒语,但不知道如何使它工作。在每种情况下,诊断报告都具有与其他报告相同的默认特征。

<my:HeaderVisual x:Class="MyApplication.ReportsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:my="clr-namespace:MyApplication">

   <DataGrid Name="_dgReport"
                ColumnWidth="Auto"
                CanUserAddRows="False"
                VerticalScrollBarVisibility="Auto"
                HorizontalScrollBarVisibility="Auto"
                ItemsSource="{Binding}"
                IsReadOnly="True">
      <DataGrid.Resources>
         <Style TargetType="DataGridCell">
            <Setter Property="TextBlock.TextAlignment" Value="Center"></Setter>
            <Style.Triggers>
               <!-- Override some property settings for Diagnostics reports... -->
               <!--
                  <DataTrigger Binding="{Binding my:ReportSettingsData.SelectedReport}"  Value="DiagSummary">
                  <DataTrigger Binding="{Binding Path=my:ReportSettingsData.SelectedReport}"  Value="DiagSummary">
               -->
               <Trigger Property="my:ReportSettingsData.SelectedReport"  Value="DiagSummary">
                  <Setter Property="TextBlock.TextAlignment" Value="Left"></Setter>
               </Trigger>
            </Style.Triggers>
         </Style>
      </DataGrid.Resources>
   </DataGrid>

</my:HeaderVisual>

我怎样才能让我的TriggerReportSettingsData.SelectedReport == SelectedReportType.DiagSummary 时触发?

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    如何使 XAML 中的样式以您自己的 C# 代码中的变量为条件

    我建议您查看 CellTemplate 选择器 (GridViewColumn.CellTemplateSelector Property (System.Windows.Controls)),您可以在其中在后面的代码中执行选择逻辑。

    粗略示例

    只需定义资源中所需的模板(4 个,但显示了两个)

    <Window.Resources>
        <DataTemplate x:Key="EquipTemplate">
    
            <TextBlock Margin="2" Text="Equip" Foreground="Green"/>
    
        </DataTemplate>
    
        <DataTemplate x:Key="EventTemplate">
    
            <TextBlock Margin="2" Text="Event" Foreground="Red"/>
    
       </DataTemplate>
    
        <DataTemplate x:Key="UserTemplate" ...
    
    </Window.Resources>
    

    网格单元格的 Xaml 模板使用选择器

    <DataGridTemplateColumn Header="My Event">
        <DataGridTemplateColumn.CellTemplateSelector>
            <local:SelectedReportTypeTemplateSelector
                EquipTemplate="{StaticResource EquipTemplate}"
                EventTemplate="{StaticResource EventTemplate}"
                User...
            />
        </DataGridTemplateColumn.CellTemplateSelector>
    </DataGridTemplateColumn>
    

    代码背后

    public class MeetingTemplateSelector : DataTemplateSelector
        {
            public DataTemplate EquipTemplate { get; set; }
    
            public DataTemplate EventTemplate { get; set; }
    
            public DataTemplate UserTemplate { get; set; }
    
            protected override DataTemplate SelectTemplateCore(object item, 
                      DependencyObject container)
            {
               DataTemplate result;
    
               switch( ((ReportSettingsData) item).SelectedReport)
               {
                    case EquipSummary : result = EquipTemplate; break;
                    case EventSummary : result = EventTemplate; break;
                    case UserSummary ..
               }
    
              return result;
            }
        }
    

    更新

    根据评论,选择的多样性使模板建议增长到 30 多个模板。另一种方法可能是使用操作属性来扩展目标类来代替触发的操作。例如,假设我们需要显示红色,在实例上提供它并绑定。

    public Partial MyClassInstance
    {
        public Brush ColorMeAs 
        {
             get { return this.IsValid ? BrushGreen  : BrushRed; }
        }
        ... other properties as such:
    }
    

    然后这样绑定

    Foreground="{Binding ColorMeAs}"
    

    触发器

    这是从我的存档中提取的数据触发器的示例:

    <Style x:Key="LabelStyle" TargetType="{x:Type Label}">
        <Setter Property="VerticalAlignment" Value="Top" />
        <Setter Property="Width" Value="80" />
        <Setter Property="Height" Value="28"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=LoginInProcess}" Value="True">
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=LoginInProcess}" Value="False">
                <Setter Property="IsEnabled" Value="True" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    【讨论】:

    • 我不确定您说 XAML 有多久了,但对我来说这是一门外语。我尝试用&lt;DataTemplate... 包装我的&lt;Style... 块,而XAML 编辑器不喜欢这样。我不太确定如何使用模板。我正在尝试编辑一个大的“遗留”界面,我害怕改变太多而破坏东西。当前代码使用&lt;DataGrid.Resources&gt; 块内的&lt;Style... 块,该块本身位于&lt;DataGrid&gt; 块内。 &lt;Trigger&gt;&lt;DataTrigger&gt;还有其他现有用途;我可以坚持这种方法吗?
    • 即使我使用了模板,也有 20 个DataGrid 属性和 5 个DataGridColumnHeader 属性在所有报告类型之间通用;区别在于DataGridCell 属性,其中&lt;Style TargetType="DataGridCell"&gt; 块设置了6 个属性;我只想将其中 3 个属性更改为诊断报告的特殊属性。使用您的模板技术,我是否必须在两个模板中定义所有 31 个属性,一个名为“DefaultTemplate”,另一个名为“DiagnosticsTemplate”?或者诊断模板是否可以从默认模板派生?
    • @phonetagger 您可以继续使用触发器,正如您所见,它们很方便。请参阅有关解决触发器的替代方法的更新。
    • 好的,这也很好知道...但是如果我不想解决触发器怎么办?你能告诉我如何使我的SelectedReport 变量可用作触发器属性吗?如果当我在 XAML 编辑器中键入“...Value="”时,Intellisense 会弹出枚举的有效值(EquipSummary、UserSummary、EventSummary 和 DiagSummary),那将特别酷。似乎应该有办法做到这一点。
    • @phonetagger 上面显示的是触发代码。注意SelectedReport 因为它是一个枚举,所以该值可能是一个整数而不是可读的枚举项。 HTH
    【解决方案2】:

    您的 C# 代码看起来不错,并且您已经拥有引用 MyApplication 命名空间的 XML 命名空间声明,所以:

    您应该能够使用枚举标识符使用x:Static 标记访问枚举值,如本示例所示(我喜欢这个示例,因为它还展示了如何使用非自定义 x:Static 以及如何做一些树遍历):

    <DataTemplate.Triggers>
      <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
          <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True" />
          <Condition Binding="{Binding Type}" Value="{x:Static loc:AppProfileItemType.Custom}" />
        </MultiDataTrigger.Conditions>
        <MultiDataTrigger.Setters>
          <Setter TargetName="PART_Delete" Property="Visibility" Value="{x:Static Visibility.Visible}" />
        </MultiDataTrigger.Setters>
      </MultiDataTrigger>
    </DataTemplate.Triggers>
    

    在您的情况下,您的枚举标记应该是:

    Value="{x:Static my:SelectedReportType.DiagSummary}"

    【讨论】:

    • 感谢蒂姆,它确实有效!不幸的是,它只工作一次。似乎触发器只被评估一次,无论变量的初始状态是什么,这就是定义触发器是否触发的原因。有没有办法让 XAML 再次被评估,或者在变量发生变化时评估?另外,花括号背后的含义是什么,为什么我需要花括号围绕值而不是属性?
    • 顺便说一句,当该变量的状态发生更改时,具有此控件的屏幕不会处于活动状态。它只需要在屏幕显示以显示报告结果时进行评估/解析或其他任何操作。然后,报告屏幕将在状态再次更改之前关闭。
    • 啊,我完全没有注意到你没有使用 DependencyProperty 模式实现你的属性,我的错!我看到你确实正确地使类扩展了 DependencyObject,这是正确的第一次开始(许多新工程师在没有从 DO 扩展的情况下实现 DP,然后挠头,你反其道而行之)。如果您使用的是 Visual Studio,只需键入“propdp”并点击 Tab,自动模板将为您创建 DP,并为您提供热编辑框以输入您的类型、名称、所有者和默认值。将现有属性更改为此格式。
    • 将现有属性更改为 DP 后,它们应该开始向 XAML 绑定和侦听器发送正确的更新通知。 (并且您的触发器将随着值自动更改而更新,这就是 MVVM 的强大功能。)顺便说一句,花括号中的代码称为 XAML 标记,它是 XAML 中的一种子语言,允许您在可视化树之间进行反射(你的 XAML)和逻辑树(你的数据对象又名 ModelView)。
    • 感谢您的宝贵时间。我已经让它工作了,完全在代码隐藏中实现。代码看起来不再像上面那样了。但万一我有时间再试一次,我不确定你在说什么属性我没能做一个 DP。当然Instance 属性无关紧要,不是吗? SelectedReport 已经得到了 DP 的支持,如果我做得对的话(我不知道 'propdp' 选项卡的事情,所以我只是从其他地方复制粘贴;然后因为这个类是一个单例和 'SelectedReport ' 是静态属性,我在 getter & setter 中添加了 '.Instance'。
    猜你喜欢
    • 1970-01-01
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    • 2019-10-24
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    • 2015-12-19
    相关资源
    最近更新 更多