0

so what am trying to do is to display a TextBlock, using a different color for each line, and ideally I would like to use binding.

My TextBlock may display a list of Items, each Item has Texte and Color property. With a foreach, I want to display one line per item, with the Texte property, in specified Color.

I already tried the following :

1)I make a TextBlock, whose Text is binded to a string property in ViewModel, and with a foreach I just fill the string, but in that case cannot apply the colors on each line as I want.

2)I found that link on stack that helped a bit, where it is advised to use the Run.

So in my ViewModel I fill the TextBlock like that :

private TextBlock legende;
public TextBlock Legende
{
   get { return legende; }
   set
   {
     legende = value;
     this.NotifyPropertyChanged("Legende");
   }
}
public void UpdateLegend(Legend legende)
{
    this.Legende = new TextBlock();
    this.Legende.TextWrapping = TextWrapping.Wrap;
    this.Legende.Margin = new Thickness(10);
    this.Legende.FontSize = 14;
    this.Legende.LineStackingStrategy=LineStackingStrategy.BlockLineHeight;
    this.Legende.LineHeight = 20;
    int i = 0;
    foreach(LegendItem item in legende.list)
    {
      if(i==0)
      {
        Run run = new Run(item.Texte);
        run.Foreground = item.Color;
        this.Legende.Inlines.Add(run);
      }
      else
      {
         Run run = new Run(")\r\n"+item.Texte);
         run.Foreground = item.Color;
         this.Legende.Inlines.Add(run);
      }

      i++;
    }
}

My Model does the following, each time I need to update Legend I do :

contexte.UpdateLegend(MonGraphe.Legende);
this.CanvasLegend = contexte.Legende;

Then my View :

<Grid Grid.Column="2" Background="WhiteSmoke">
    <TextBlock x:Name="CanvasLegend" TextAlignment="Left" HorizontalAlignment="Left"/>
</Grid>

For now it is not working at all and I cannot find why. I also would like to do everything in my ViewModel, but for that I would need to bind my Textlock to a TextBlock defined in my ViewModel, but cannot find how to do that?

EDIT :

As explained by Icebat I am using converter as the following :

XML identical to IceBat

ViewModel :

public class ColoringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //is your collection IEnumerable<>? If not, adjust as needed
            var legende = value as Legend;
            if (legende == null) return value;

            return legende.list.Select(i => new Run() { Text = i.Texte, Foreground = i.Color }).ToArray();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    class ViewModelMainWindow : INotifyPropertyChanged
    {
        private Legend legende;
        public Legend Legende
        {
            get { return legende; }
            set
            {
                legende = value;
                this.NotifyPropertyChanged("Legende");
            }
        }
}

Also include my "Legende" class, as I think this is where there is missunderstanding :

public class LegendItem 
    {
        public int Type { get; set; }
        public double Diametre { get; set; }
        public double Longueur { get; set; }
        public double Profondeur { get; set; }
        public string Texte { get; set; }
        public Brush Color { get; set; }
        public LegendItem()
        {

        }
    }
    public class Legend : INotifyPropertyChanged
    {
        private ObservableCollection<LegendItem> liste;
        public ObservableCollection<LegendItem> Liste
        {
            get { return liste; }
            set
            {
                liste=value;
                NotifyPropertyChanged(ref liste, value);
            }
        }
        public Legend()
        {
            this.list = new ObservableCollection<LegendItem>();
        }
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
        private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
        {
            if (object.Equals(variable, valeur)) return false;

            variable = valeur;
            NotifyPropertyChanged(nomPropriete);
            return true;
        }
        public Legend(Repere rep)
        {
            List < Brush >  listColors = new List<Brush>();
            listColors.Add(Brushes.Red);
            listColors.Add(Brushes.MediumBlue);
            listColors.Add(Brushes.Purple);
            listColors.Add(Brushes.LimeGreen);
            listColors.Add(Brushes.DarkOrange);
            listColors.Add(Brushes.Navy);
            listColors.Add(Brushes.DarkRed);
            listColors.Add(Brushes.Chartreuse);
            listColors.Add(Brushes.DodgerBlue);
            listColors.Add(Brushes.Tomato);

            this.list = new ObservableCollection<LegendItem>();
            List<Percage> listPer = rep.liste_percages;
            List<GroupePercage> listeGp = rep.listeGpPercages;


            foreach (Percage per in listPer)
            {
                LegendItem itemExists = this.list.FirstOrDefault(x => x.Type == per.Type && x.Diametre == per.Diametre && x.Profondeur == per.Depth&&x.Longueur==per.Longueur);
                if (itemExists == null)
                {
                    LegendItem newItem = new LegendItem();

                    newItem.Type = per.Type;
                    switch (newItem.Type)
                    {
                        case 51: newItem.Texte = "DRILL "; break;
                        case 58: newItem.Texte = "CounterSink "; break;
                        case 59: newItem.Texte = "Tapping "; break;
                        case 12: newItem.Texte = "Slot "; break;
                        default: newItem.Texte = "NOT FOUND "; break;
                    }
                    newItem.Diametre = per.Diametre;
                    newItem.Longueur = per.Longueur;
                    newItem.Texte += newItem.Diametre.ToString();
                    if (newItem.Type==12)
                    {
                        newItem.Texte = newItem.Diametre + " x " + newItem.Longueur;
                    }
                    newItem.Profondeur = per.Depth;
                    this.list.Add(newItem);
                }
            }
            foreach (GroupePercage per in listeGp)
            {
                LegendItem itemExists = this.list.FirstOrDefault(x => x.Type == per.Type && x.Diametre == per.Diametre && x.Profondeur == per.Depth && x.Longueur == per.Longueur);
                if (itemExists == null)
                {
                    LegendItem newItem = new LegendItem();
                    newItem.Type = per.Type;
                    switch (newItem.Type)
                    {
                        case 51: newItem.Texte = "DRILL "; break;
                        case 58: newItem.Texte = "CounterSink "; break;
                        case 59: newItem.Texte = "Tapping "; break;
                        case 12: newItem.Texte = "Slot "; break;
                        default: newItem.Texte = "NOT FOUND "; break;
                    }
                    newItem.Diametre = per.Diametre;
                    newItem.Longueur = per.Longueur;
                    newItem.Texte += newItem.Diametre.ToString();
                    if (newItem.Type == 12)
                    {
                        newItem.Texte = newItem.Diametre + "x" + newItem.Longueur;
                    }
                    newItem.Profondeur = per.Depth;
                    this.list.Add(newItem);
                }
            }
            for(int i=0;i<this.list.Count();i++)
            {
                this.list[i].Color = listColors[Math.Min(i,9)];
            }
        }
    }
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34

1 Answers1

2

Well, the approach with Runs seems to be the simplest one. Not sure where you went wrong, but it all should be relatively easy. You can just use ItemsControl for inlines:

<TextBlock x:Name="CanvasLegend" TextAlignment="Left" HorizontalAlignment="Left">
    <TextBlock.Inlines>
        <ItemsControl ItemsSource="{Binding LegendItems, ElementName=me, 
                                    Converter={StaticResource coloringConverter}}" />
    </TextBlock.Inlines>
</TextBlock>

I use ElementName to point to the host Window here for simplicity, but you can bind it to your ViewModel. The converter looks like this:

public class ColoringConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
     //is your collection IEnumerable<>? If not, adjust as needed
     var legend = value as IEnumerable<LegendItem>;
     if (legend == null) return value;

     return legend.Select(i => new Run() { Text = i.Text, Foreground = i.Color }).ToArray();
  }

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

Then just add the converter in the resources somewhere (in my example it's Window):

<Window.Resources>
    <local:ColoringConverter x:Key="coloringConverter" />
</Window.Resources>
icebat
  • 4,696
  • 4
  • 22
  • 36
  • Just could solve my problem but without binding, so will try with your method, maybe I could learn something new again today :). So I all understood(thanks for ListView didn't know I could do that) except one thing : Where may I write my converter? I already tried several times to use these converters, but never could do it ^^' – Siegfried.V Dec 06 '18 at 12:16
  • 1
    @Siegfried.V, I changed `ListView` to `ItemsControl` to avoid item highlighting. You can do it without bindings, but learning to use them is very benefitial for WPF programming. You can write your converter where TextBlock is. You can even do it in Grid right there using `` instead of Window like in my example. – icebat Dec 06 '18 at 12:25
  • yes thanks, and in fact this is long I try to understand these "converters", and I think it is time... You're telling me the function public class ColoringConverter : IValueConverter is XAML code?it doesn't look like XAML? – Siegfried.V Dec 06 '18 at 12:28
  • @Siegfried.V, oh, you're talking about the code itself. You can place it in code-behind of the Window or control you're using. In bigger projects the converters usually are in separate classes grouped up somewhere. – icebat Dec 06 '18 at 13:03
  • something wrong yet, I see that I don't call the Convert(object value...) function. Where am I supposed to call it? – Siegfried.V Dec 06 '18 at 13:03
  • @Siegfried.V, the Binding calls it. You don't need explicit call, binding will take care of it. – icebat Dec 06 '18 at 13:04
  • I found it, I decided to put it in my ViewModel, more logical than getting it in my Model(but I see advantage to make a separate file, will do it as soon as I manage to use it ) – Siegfried.V Dec 06 '18 at 13:05
  • ok, I guess something is wrong in my binding, will edit my question to give more info – Siegfried.V Dec 06 '18 at 13:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/184812/discussion-between-icebat-and-siegfried-v). – icebat Dec 06 '18 at 13:23