1

WPF and XAML newbie here....

I need to tie a WPF Trigger or DataTrigger in XAML code into some C# code in a class other than the class of the XAML control. This is very frustrating as all 28,000 tutorials I've read only give a trivial example for Trigger or DataTrigger that involves properties that already exist (e.g. MouseOver), none of them give examples of how to tie it in with your own C# code.

I have a screen for displaying various report types. The XAML for all of the report types is the same, except that for diagnostic reports, my requirements are that the DataGrid cells be configured with TextBlock.TextAlignment="Left", while all other reports (i.e. the default) should be TextBlock.TextAlignment="Center". (There are a few other differences; for brevity I'll just say that's the only difference.) I really don't want to have to duplicate the entire XAML to special-case the diagnostics report, since 99% of it would be the same as the other reports.

To use a Trigger, I thought perhaps I need my class to inherit from DependencyObject so I can define DependencyProperty's in it (being a WPF newbie I realize I may be saying some really outlandish things). So in my C# code, I have a class with this...

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));
   }
}

So in my XAML file, I've played with various incantations of Trigger and DataTrigger and can't figure out how to make it work. In every case, the diagnostic report has the same default characteristics of the other reports.

<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>

How can I get my Trigger to fire when ReportSettingsData.SelectedReport == SelectedReportType.DiagSummary?

phonetagger
  • 7,701
  • 3
  • 31
  • 55

2 Answers2

2

How to make styles in XAML conditional on a variable in your own C# code

I recommend that you look into a CellTemplate Selector (GridViewColumn.CellTemplateSelector Property (System.Windows.Controls)) where you can do the selection logic in code behind.

Rough Example

Simply define the templates (4 but two shown) needed in the resource

<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 template usage selector for the grid cell

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

Code Behind

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;
        }
    }

Update

As per the comment that the variety of choices makes the template suggestion grow to over 30 templates. One other way might be to extend the target class with operational properties in lieu of the triggered actions. For example say we need a red color shown, provide it on the instance and bind.

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

then bind as such

Foreground="{Binding ColorMeAs}"

Triggers

Here is an example of a data trigger pulled from my archive:

<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>
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • I'm not sure how long you've been speaking XAML, but to me this is a foreign language. I tried wrapping my `` block that's itself inside a `` block. There are other existing uses of `` and ``; can I stick with that approach? – phonetagger Jul 24 '15 at 17:45
  • Even if I used templates, there are 20 `DataGrid` properties and 5 `DataGridColumnHeader` properties common between all report types; the differences are in the `DataGridCell` properties, where the ` – phonetagger Jul 24 '15 at 18:06
  • @phonetagger You can continue to use triggers, they are handy as you have seen. See update on an alternate way to work around triggers. – ΩmegaMan Jul 24 '15 at 18:37
  • OK, that's good to know too... but what if I don't want to work around triggers? Can you tell me how to make my `SelectedReport` variable available for use as a trigger property? It would be especially cool if, as I were typing "...`Value="`" into the XAML editor, Intellisense would pop up the valid values of the enum (EquipSummary, UserSummary, EventSummary, and DiagSummary). It seems like there should be a way to make that happen. – phonetagger Jul 24 '15 at 19:39
  • @phonetagger Shown above is a trigger code. Note for `SelectedReport` since it is a enum, the value may be an integer instead of the readable enum item. HTH – ΩmegaMan Jul 24 '15 at 20:35
  • Do you have the accompanying C# code that goes along with your trigger example? What does the function/class/property named "LogInProcess" look like? Also, if your example could be for something that's an enum rather than a bool, that would be really nice. – phonetagger Jul 24 '15 at 23:19
  • @phonetagger `LoginProcess` is a property which is a `Boolean` which can be either `true` or `false`. – ΩmegaMan Jul 25 '15 at 00:51
  • @phonetagger Enums are tricky. See this response about binding and enums [How can i use enum types in XAML?](http://stackoverflow.com/a/14279653/285795) – ΩmegaMan Jul 25 '15 at 00:58
  • Enums are anything but tricky, and make it very easy to organize enumerated data and to enforce state and referential integrity within your project. What is tricky are TemplateSelectors and even very senior MVVM engineers tend to shy away from them as they are often way more work than they're worth. You need to have a very good reason to go through the hoops and effort of creating TemplateSelectors. – tpartee Jul 29 '15 at 23:46
0

Your C# code looks fine, and you already have the XML NameSpace declaration referencing your MyApplication namespace, so:

You should just be able to access the enum value using the x:Static markup using the enum identifier as shown in this example (I like this example because it also shows how to use non-custom x:Static and how to do some tree traversal as well):

<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>

In your case your Markup for your enum should be:

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

tpartee
  • 548
  • 7
  • 20
  • Thanks Tim, it actually works! Unfortunately it only works once. It appears that the Trigger is evaluated only once, and whatever the initial state of the variable is, that's what defines whether the trigger fires or not. Is there a way to get the XAML to be evaluated again, or perhaps evaluated whenever the variable changes? Also, what's the meaning behind the curly braces, and why would I need curly braces around the Value but not the Property? – phonetagger Jul 28 '15 at 21:52
  • BTW, the screen with this control is not active when the state of that variable is changed. It would only have to be evaluated/parsed or whatever when the screen is displayed to show the results of a report. The report screen would then be closed before the state could change again. – phonetagger Jul 28 '15 at 22:25
  • Ah, I completely failed to notice that you didn't implement your properties using the DependencyProperty pattern, my bad! I see that you DID properly make the class extend DependencyObject which is the right first start (many new engineers implement the DP without extending from DO and then scratch their heads, you went the other-way 'round). If you're using Visual Studio, just type 'propdp' and hit Tab and an auto-template will create the DP for you and give you hot-edit boxes to enter your type, name, owner and default value. Change your existing properties to this format. – tpartee Jul 29 '15 at 23:01
  • Once you've changed your existing properties to DPs, they should start sending the proper update notifications to your XAML bindings and listeners. (And your triggers will update as the value changes automagically, this is the power of MVVM.) BTW the code in the curly braces is called XAML Markup, it is a sub-language inside XAML which allows you to do reflection between the visual tree (your XAML) and the logical tree (your data object aka ModelView). – tpartee Jul 29 '15 at 23:03
  • Thanks for your time. I've already got it working, implemented entirely in the code-behind. The code doesn't look like it did above anymore. But in case I try this again sometime, I'm not sure what properties you're saying I failed to make a DP. Surely the `Instance` property doesn't matter, does it? The `SelectedReport` is already backed by a DP, if I did it right (I didn't know about the 'propdp' tab thing so I just copy-pasted from somewhere else; then because this class was a singleton and the 'SelectedReport' is a static property, I added the '.Instance' in the getter & setter. – phonetagger Jul 29 '15 at 23:46
  • Were my comments about DPs clear? Did converting your CLR properties to DPs fix your issue? Let me know! – tpartee Jul 30 '15 at 17:51
  • What properties are you talking about? Isn't there only one, and isn't it already a DP? (SelectedReport --> SelectedReportProperty) I think I already made it a DP. Did I mess it up? Thanks by the way. – phonetagger Jul 30 '15 at 20:49
  • Ah, I didn't recognize the DP you have for a couple of reasons: one it's not at the top of the class and I'm used to only ever seeing props at the very top of a class definition (it's probably just a traditional and cultural thing, though - almost every C# engineer I've worked with does this), and second the DP template you're using is not the standard one created by the 'propdp' auto-template in Visual Studio, so the format is... different enough to register as "unusual, might not be a DP" to my brain's pattern-match for a DP. My bad! But your code should be working. =( – tpartee Jul 30 '15 at 21:13