0
votes

I’m trying to produce a FlowDocument, and want certain text runs to be highlighted when they contain a particular string. I’m using the BindableRun class to ensure I can update the Run text values (although I only set them at construction, so a vanilla instance of Run should be okay).

Essentially, I want to do this:

 <FlowDocument Name="FlowDoc" FontFamily="{x:Static SystemFonts.CaptionFontFamily}" 
                  Background="{x:Static SystemColors.ControlBrush}" 
                  FontSize="{x:Static SystemFonts.SmallCaptionFontSize}"
                  TextAlignment="Left">
      <FlowDocument.Resources>
        <Style TargetType="{x:Type local:BindableRun}">
          <!-- 1. Style all BindableRuns with a pink background by default -->
          <Setter Property="Background" Value="HotPink"/>

          <Style.Triggers>

            <!-- 2. Style BindableRuns where Text=Hello to have a green background-->
            <DataTrigger Binding="{Binding BoundText}" Value="Hello">
              <Setter Property="Background" Value="GreenYellow"/>
            </DataTrigger>
            <!-- 3. Fire a Datatrigger with parameterised converter to change 
                  text to Orange if the text contains 'StackOverflow'-->
            <DataTrigger Binding="{Binding BoundText, 
                           Converter={StaticResource ContainsBoolConverter},
                           ConverterParameter=StackOverflow}" Value="False">
              <Setter Property="Foreground" Value="Orange"/>
            </DataTrigger>
          </Style.Triggers>
        </Style>
      </FlowDocument.Resources>
    </FlowDocument>

Now, when I try and use the Xaml above:

• The style to change the background of the BindableRun objects (i.e., #1) works fine – so the contents of the FlowDocument are what I’m expecting

• The DataTriggers against the BountText property of the BindableRun objects don’t work.

• The converter isn’t even called (I’ve put a breakpoint on it) so it looks like the binding/datatrigger just isn’t firing.

Anyone done anything like this before? If so, have you managed to get it to work? There’s not so many FlowDocument examples on SO etc so I’ve not managed to track down anyone else who’s attempted (either successfully or not) to use DataTriggers on the Run's Text property.

For reference, here's the Contains converter that I'm using (although it never fires at all, so it doesn't matter if it's right or not ;)).

public class ContainsTextConverter : IValueConverter
{
    public object Convert( object value, Type t, object parameter, CultureInfo culture )
    {
        String input = value.ToString();
        string param = parameter.ToString();

        return input.IndexOf( param, StringComparison.OrdinalIgnoreCase ) != -1;
    }

    public object ConvertBack( object value, Type t, object parameter, CultureInfo culture )
    {
        // Don't care about this
        throw new NotSupportedException();
    }
};
2

2 Answers

1
votes

Add RelativeSource={RelativeSource Self} to both of your DataTrigger bindings. By default a binding looks for a property of the control's DataContext. To make it look for a property of the control you need the RelativeSource parameter.

    <!-- 2. Style BindableRuns where Text=Hello to have a green background-->
    <DataTrigger Binding="{Binding BoundText, RelativeSource={RelativeSource Self}}" Value="Hello">
        <Setter Property="Background" Value="GreenYellow"/>
    </DataTrigger>

    <!-- 3. Fire a Datatrigger with parameterised converter to change text to Orange if the text contains 'StackOverflow'-->
    <DataTrigger Binding="{Binding BoundText, RelativeSource={RelativeSource Self},
            Converter={StaticResource ContainsTextConverter},
            ConverterParameter=StackOverflow}" Value="True">
        <Setter Property="Foreground" Value="Orange"/>
    </DataTrigger>

That will fix the problem with "Hello" not having a green background.

Now, your converter example has a couple additional problems:

  1. The converter is named ContainsTextConverter but you reference {StaticResource ContainsBoolConverter}.
  2. You specify Value="False" for the condition when you said you did want the text to contain the parameter.
  3. You need to make sure value is not null in the converter. It gets called for each of your BindableRuns, but could get called with null if the XAML is loaded before the DataContext is set.
public object Convert( object value, Type t, object parameter, CultureInfo culture )
{
    if( value != null )
    {
        return (value as String).IndexOf( parameter as String, StringComparison.OrdinalIgnoreCase ) != -1;
    }
    else return false;
}

Make those changes and you'll see that both your DataTriggers work.

0
votes

To earn a check of one word, you need to write a trigger like this:

<Trigger Property="BindableText" Value="text">
    <Setter Property="Background" Value="GreenYellow" />
</Trigger>   

Now, if the string is text, then triggered our trigger.

About the Converter, I tried different options, but the value that is passed to the Converter was always Null. And then I decided to go the other way. After, you create a class for the property BindableText, so I decided to add to it two more:

  1. One property to verify the string that you passed to the converter (StackOverflow string).

  2. One property of type Brush, to set the color.

So, my full example:

XAML

<Grid>
    <FlowDocumentScrollViewer>
        <FlowDocument>
            <FlowDocument.Resources>
                <Style TargetType="{x:Type local:BindableExtender}">
                    <Style.Triggers>
                        <Trigger Property="BindableText" Value="text">
                            <Setter Property="Background" Value="GreenYellow"/>
                        </Trigger>                                                        
                    </Style.Triggers>
                </Style>
            </FlowDocument.Resources>

            <Paragraph>
                Binding string:

                <local:BindableExtender BindableText="{Binding ElementName=MyTextBox, Path=Text}"
                                        CompareText="StackOverflow"
                                        ForegroundBrush="OrangeRed" />
            </Paragraph>

            <BlockUIContainer>
                <TextBox Name="MyTextBox" Text="text"/>
            </BlockUIContainer>
        </FlowDocument>
    </FlowDocumentScrollViewer>
</Grid>

Code behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class BindableExtender : Run
{
    #region BindableText declaration

    public String BindableText
    {
        get 
        { 
            return (string)GetValue(BindableTextProperty); 
        }

        set 
        { 
            SetValue(BindableTextProperty, value); 
        }
    }

    public static readonly DependencyProperty BindableTextProperty =
        DependencyProperty.Register("BindableText",
            typeof(string),
            typeof(BindableExtender),
            new UIPropertyMetadata(null, BindableTextProperty_PropertyChanged));

    private static void BindableTextProperty_PropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        BindableExtender MyRun = dependencyObject as BindableExtender;

        if (dependencyObject is BindableExtender)
        {
            MyRun.Text = (string)e.NewValue;
            string bind = (string)e.NewValue;

            // Check the two strings
            if (bind.IndexOf(MyRun.CompareText, StringComparison.OrdinalIgnoreCase) != -1)
            {
                MyRun.Foreground = MyRun.ForegroundBrush;
            }
            else
            {
                MyRun.Foreground = Brushes.Black;
            }
        }
    }

    #endregion BindableText declaration

    #region CompareText declaration

    public String CompareText
    {
        get 
        { 
            return (string)GetValue(CompareProperty); 
        }

        set 
        {
            SetValue(CompareProperty, value); 
        }
    }

    public static readonly DependencyProperty CompareProperty =
        DependencyProperty.Register("CompareText",
            typeof(string),
            typeof(BindableExtender),
            new UIPropertyMetadata(null, CompareProperty_PropertyChanged));

    private static void CompareProperty_PropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        BindableExtender MyRun = dependencyObject as BindableExtender;
        string CompareString = e.NewValue as string;

        // Save the value in CompareText property
        MyRun.CompareText = CompareString;
    }

    #endregion

    #region ForegroundBrush declaration

    public Brush ForegroundBrush
    {
        get
        {
            return (Brush)GetValue(ForegroundBrushProperty);
        }

        set
        {
            SetValue(ForegroundBrushProperty, value);
        }
    }

    public static readonly DependencyProperty ForegroundBrushProperty =
        DependencyProperty.Register("ForegroundBrush",
            typeof(Brush),
            typeof(BindableExtender),
            new UIPropertyMetadata(null, ForegroundBrushProperty_PropertyChanged));

    private static void ForegroundBrushProperty_PropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        BindableExtender MyRun = dependencyObject as BindableExtender;
        Brush NewForegroundBrush = e.NewValue as Brush;

        // Save the value in ForegroundBrush property
        MyRun.ForegroundBrush = NewForegroundBrush;
    }

    #endregion
}

Output #1

enter image description here

Output #2

enter image description here