0

I have a ListView looking like that:

 <ListView  Name="myLV"  ItemsSource="{Binding myObservableCollection}" IsSynchronizedWithCurrentItem="True" >
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="v1" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding var1}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="v2" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding var2}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="v3" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding var3}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>

Each row will have a set of three strings, which look for example like that:

  • String 1: aa-bbbb-cc
  • String 2: ax-bbbb-cd
  • String 3: aa-bbbb-ce

Now I want a seperate text color for the three strings partially on the positions where they differ. In the example above, I would have to color the second and the 10th position of each of the three strings.

I have no idea how start on this task, because it is even hard for me to google that problem. Any idea?

Julian
  • 185
  • 1
  • 15
  • You can split the textbox into multible runs () and use a datatrigger to achive that – Denis Schaf Feb 14 '19 at 07:19
  • @DenisSchaf With this solution I would have to set the Tags statically into XAML? But I dont know how many positions of my strings need to be colored? Maybe zero, maybe 5 or completeley – Julian Feb 14 '19 at 07:27
  • mhh...by the fact that you want to look at each caracter individually it makes sense in my eys to also display them individually in runs as you say. Also if your template contains more runs than characters are in your string it wont hurt, as they would contain an empty string and be invisible – Denis Schaf Feb 14 '19 at 07:35
  • @DenisSchaf That would mean, If i want to support a maximum length of 20 characters per string, i would have to set 20 tags after each other. That does not sound really smart, does it? – Julian Feb 14 '19 at 07:48
  • instead of binding the textbox to a string you could bind it to an observable collection of runs that would make the code look nicer and give you the chance to mange the runcolors in code instead of XAML – Denis Schaf Feb 14 '19 at 08:04
  • Note that the Inlines property of the TextBlock class is not a dependency property. You can't bind it. You may however declare an attached property that is bound with a converter that creates an Inline collection, and which updates the target element's Inlines property. See e.g. here: https://stackoverflow.com/q/1959856/1136211 – Clemens Feb 14 '19 at 09:13
  • @Clemens I followed your link and tried to implement the solution from Frank A. Now I replaced in my XAML TextBlock with BindableTextBlock but I dont know what to bind and how to tell which part of the string gets which format – Julian Feb 14 '19 at 13:44

1 Answers1

1

You could create a user control that splits the text into four parts and allows you to style them independently. The following sample control takes a Text as input and stores the parts in dedicated properties:

public partial class MyFormattedTextControl : UserControl, INotifyPropertyChanged
{
    public MyFormattedTextControl()
    {
        InitializeComponent();
        stack.DataContext = this;
    }

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(MyFormattedTextControl), new PropertyMetadata(null, OnTextPropertyChanged));

    private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctrl = (MyFormattedTextControl)d;
        var t = (string)e.NewValue;
        var regex = new Regex("(?<Part1>.)(?<Part2>.)(?<Part3>.{7})(?<Part4>.)");
        var m = regex.Match(t);
        if (!m.Success)
        {
            ctrl.Part1 = ctrl.Part2 = ctrl.Part3 = ctrl.Part4 = string.Empty;
        }
        else
        {
            ctrl.Part1 = m.Groups["Part1"].Value;
            ctrl.Part2 = m.Groups["Part2"].Value;
            ctrl.Part3 = m.Groups["Part3"].Value;
            ctrl.Part4 = m.Groups["Part4"].Value;
        }
    }

    private string part1;

    public string Part1
    {
        get { return part1; }
        set { part1 = value; OnPropertyChanged(); }
    }

    private string part2;

    public string Part2
    {
        get { return part2; }
        set { part2 = value; OnPropertyChanged(); }
    }


    private string part3;

    public string Part3
    {
        get { return part3; }
        set { part3 = value; OnPropertyChanged(); }
    }

    private string part4;

    public string Part4
    {
        get { return part4; }
        set { part4 = value; OnPropertyChanged(); }
    }

    private void OnPropertyChanged([CallerMemberName] string callerMember = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerMember));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

In the XAML file, the user control displays the parts in separate text blocks that you can style independently:

<StackPanel x:Name="stack" Orientation="Horizontal">
    <TextBlock Text="{Binding Part1}" />
    <TextBlock Text="{Binding Part2}" Foreground="Red" />
    <TextBlock Text="{Binding Part3}" />
    <TextBlock Text="{Binding Part4}" Foreground="Green" />
</StackPanel>

In your ListView, you use the user control instead of the TextBlock:

<DataTemplate>
  <local:MyFormattedTextControl Text="{Binding var1}" />
</DataTemplate>

Update: Formatting the blocks based on data values

In order to provide the pattern with the row values, you can add another dependency property to the user control:

public IEnumerable<bool> PartFlags
{
    get { return (bool[])GetValue(PartFlagsProperty); }
    set { SetValue(PartFlagsProperty, value); }
}

// Using a DependencyProperty as the backing store for PartFlags.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty PartFlagsProperty =
    DependencyProperty.Register("PartFlags", typeof(bool[]), typeof(MyFormattedTextControl), new PropertyMetadata(new bool[] { false, false, false, false }));

In order to convert the bools to a color, you can create a custom value converter, e.g.

public class PartColorValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var b = value as bool[] ?? new bool[] { };
        var i = System.Convert.ToInt32(parameter);
        return b.ElementAtOrDefault(i) ? Brushes.Red : Brushes.Black;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

You can use this converter in the user control to apply the color to the parts:

<UserControl x:Class="MyWpfApp.MyFormattedTextControl"
             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:MyWpfApp"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:PartColorValueConverter x:Key="partColorValueConv" />
    </UserControl.Resources>
    <StackPanel x:Name="stack" Orientation="Horizontal">
        <TextBlock Text="{Binding Part1}" Foreground="{Binding PartFlags, Converter = {StaticResource partColorValueConv}, ConverterParameter=0}" />
        <TextBlock Text="{Binding Part2}" Foreground="{Binding PartFlags, Converter = {StaticResource partColorValueConv}, ConverterParameter=1}" />
        <TextBlock Text="{Binding Part3}" Foreground="{Binding PartFlags, Converter = {StaticResource partColorValueConv}, ConverterParameter=2}" />
        <TextBlock Text="{Binding Part4}" Foreground="{Binding PartFlags, Converter = {StaticResource partColorValueConv}, ConverterParameter=3}" />
    </StackPanel>
</UserControl>

To test, I've added a fourth value to the tuple that contains the pattern (in my case, a randomly generated pattern):

<local:MyFormattedTextControl Text="{Binding Item1}" PartFlags="{Binding Item4}" />
Markus
  • 20,838
  • 4
  • 31
  • 55
  • This solution would be useful, if I could tell the MyFormattedTextControl, which parts of the string need to be colored. Is it possible to deliver additionally to var1 an array like int[1,0,0,1,1], which means red, black, black, red, red. This array would be member of the same instance as var1 – Julian Feb 15 '19 at 08:58
  • @JulianHerbel there are several ways to achieve this. If you have a common logic, like "each a und c red, everything else black", you can extend the control and implement the logic there. Otherwise, you could add another dependency property to the control that takes the array and bind it to the corresponding array for the line. – Markus Feb 15 '19 at 09:10
  • This is the point. I dont know how to extend your class correctly to have a second dependency property for my array int[] I played around for a while but I dont get access to the array. Can you give me a hint? – Julian Feb 15 '19 at 09:16
  • @Julian I've updated the answer with a sample. Hope this helper. – Markus Feb 15 '19 at 10:33