Rectangle inside DataGrid, bind width value of rectangle

Viewed 169

I am trying to bind rectangle width property to a variable so with each added record to Data Grid the width of rectangle is different (chart style). I cannot even access this rectangle with x:Name.

XAML:

<DataGrid.Columns>
   <DataGridTextColumn Binding="{Binding Path=Data}" Header="Data"  ></DataGridTextColumn>
   <DataGridTextColumn Binding="{Binding Path=Pamaina}" Header="Pamaina"></DataGridTextColumn>
   <DataGridTextColumn Binding="{Binding Path=TyrimoVieta}" Header="Tyrimo vieta"></DataGridTextColumn>
   <DataGridTextColumn Binding="{Binding Path=Koncentracija}" Header="Koncentacija"></DataGridTextColumn>
   <DataGridTemplateColumn Header="Rect">
      <DataGridTemplateColumn.CellTemplate>
         <DataTemplate>
            <StackPanel x:Name="assTR">
               <Rectangle Width="{Binding ilgis}" Height="10" Fill="Green" />
            </StackPanel>
         </DataTemplate>
      </DataGridTemplateColumn.CellTemplate>
   </DataGridTemplateColumn>
</DataGrid.Columns>

C#

List<DatagridItems> datagridItems = new List<DatagridItems>();
public class DatagridItems
{
   public string Data { get; set; }
   public string Pamaina { get; set; }
   public string TyrimoVieta { get; set; }
   public string Koncentracija { get; set; }
}

public List<DatagridItems> LoadCollectionData(string data, string pamaina, string tyrimovieta, string koncentracija)
{
   Datagrid_1.Items.Refresh(); //du refresh kad atsinaujintu kolekcija
   datagridItems.Add(new DatagridItems()
   {
      Data = data,
      Pamaina = pamaina,
      TyrimoVieta = tyrimovieta,
      Koncentracija = koncentracija,
   });
   Datagrid_1.Items.Refresh();

   return datagridItems;
}

Call to populate Data Grid

Datagrid_1.ItemsSource = LoadCollectionData(datosFormatas, row["pamaina"].ToString(), tyrimoVieta_isFunkcijos, koncentracija);

I do not actually know if this will even do what I want. Below is the picture what im trying to achieve. I want value in "Koncentracija" to set width of green rectangle. Can this even be done? I managed to draw static width rectangle only.

Any help is appreciated. Image of what i am trying to achieve

3 Answers

I want value in "Koncentracija" to set width of green rectangle. Can this even be done?

Yes it can be done.

Basically you want to bind a string type variable with a variable that is gonna represent the width of your rectangle. How?

Usually width is represented in numeric values ;)

So the simplest solution - you need to change the datatype of Koncentracija property to be int or decimal as @thatguy pointed out. But to keep things simple without using converters - you have to provide a mechanism to notify your view that property has changed.

The simplest way to do that is to implement INotifyPropertyChanged interface and invoke the propertychanged event in the setter with the name of the property like so:

public class DataGridViewItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }

    public string Data { get; set; }
    public string Pamaina { get; set; }
    public string TyrimoVieta { get; set; }

    private int koncentracija;
    public int Koncentracija
    {
        get { return koncentracija; }
        set
        {
            koncentracija = value;
            RaisePropertyChanged(nameof(Koncentracija));
        }
    }
}

I made a simple view to represent adding an element to DataGrid which changes the width of a rectangle by clicking a button. Here is the View code:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <DataGrid Name="MyDataGrid" HorizontalAlignment="Left" Height="150" Margin="44,74,0,0" VerticalAlignment="Top" Width="171"/>
    <Rectangle Name="MyRectangle" Fill="Green" HorizontalAlignment="Left" Width="{Binding Koncentracija}" Height="47" Margin="350,91,0,0" Stroke="Black" VerticalAlignment="Top" />
    <Button Name="AddItemBtn" Content="AddToDataGrid" Margin="63,281,615.6,0" VerticalAlignment="Top" Width="115" Height="53" FontSize="16" Click="AddItemBtn_Click"/>
</Grid>

Now in the code behind your view (in my example "MainWindow.xaml.cs") there's a simple logic which you can adjust to your problem. Here I increment the Koncentracija value by 30, but ofc you can do that by getting the data from your desired object.

The code:

 public partial class MainWindow : Window
{
    DataGridViewItem dataItem = new DataGridViewItem();
    List<Person> people = new List<Person>() { new Person { Name = "Mark", Age = 12 }, new Person { Name = "Chris", Age = 22 } };

    public MainWindow()
    {
        InitializeComponent();
        MyDataGrid.ItemsSource = people;
        DataContext = dataItem;
    }

    private void AddItemBtn_Click(object sender, RoutedEventArgs e)
    {
        people.Add(new Person() { Name = "Tom", Age = 27 });
        dataItem.Koncentracija += 30;
        MyDataGrid.Items.Refresh();
    }
}

Here's a picture how it looks https://ibb.co/B4FCD6S Hope it helps :)

*Edit: I recommend you to get familiar with MVVM pattern when creating WPF apps as it will help you a lot. There is a tutorial on this here: https://www.tutorialspoint.com/mvvm/index.htm

You need to create a custom DataGridTemplateColumn. You can put whatever you want inside of the DataTemplate.

This works:

MainWindow.xaml

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" IsReadOnly="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}">
        </DataGridTextColumn>
        <DataGridTemplateColumn Header="Width">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Rectangle Width="{Binding Width}" Height="10" Fill="Blue" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

MainWindowVM.cs

using GalaSoft.MvvmLight;
using System.Collections.Generic;

namespace WpfApp1
{
    public class MainWindowVM : ObservableObject
    {
        private List<Item> _Items;
        public List<Item> Items
        {
            get { return _Items; }
            set
            {
                if (value != _Items)
                {
                    _Items = value;
                    RaisePropertyChanged();
                }
            }
        }
        public MainWindowVM()
        {
            Items = new List<Item>()
            {
                new Item() {Name = "one", Width = 30.5 },
                new Item() { Name = "two", Width = 10.2 }
            };

        }
    }

    public class Item : ObservableObject
    {
        private string _Name;
        public string Name
        {
            get { return _Name; }
            set
            {
                if (value != _Name)
                {
                    _Name = value;
                    RaisePropertyChanged();
                }
            }
        }

        private double _Width;
        public double Width
        {
            get { return _Width; }
            set
            {
                if (value != _Width)
                {
                    _Width = value;
                    RaisePropertyChanged();
                }
            }
        }
    }
}

Result:

Datagrid with two columns. Second column has variable width rectangles

Ideally, the Koncentracija property should be of type double and not string, but for that you would need to create a custom data column that provides support for numeric types and also validation to prevent invalid values, an attached beahvior or a conversion between types. You can find solutions to that in this post.

This would also allow you to simply bind the Koncentracija property to the Width of the Rectangle and it would work in your example. You could align the bar graph left with HorizontalAlignment.

<Rectangle HorizontalAlignment="Left" Width="{Binding Koncentracija}" Height="10" Fill="Green" />

If you want to leave your code as it is, you could create a one-way converter for Koncentracija.

public class StringToDoubleConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      double.TryParse((string)value, out var result);
      return result;
   }

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

You would then create an instance of this converter for the DataGrid and bind the Width property to Koncentracija with the converter that automatically converts the string to double.

<DataGrid ...>
   <DataGrid.Resources>
      <local:StringToDoubleConverter x:Key="StringToDoubleConverter"/>
   </DataGrid.Resources>
   <DataGrid.Columns>
      <DataGridTextColumn Binding="{Binding Path=Data}" Header="Data"  ></DataGridTextColumn>
      <DataGridTextColumn Binding="{Binding Path=Pamaina}" Header="Pamaina"></DataGridTextColumn>
      <DataGridTextColumn Binding="{Binding Path=TyrimoVieta}" Header="Tyrimo vieta"></DataGridTextColumn>
      <DataGridTextColumn Binding="{Binding Path=Koncentracija}" Header="Koncentacija"></DataGridTextColumn>
      <DataGridTemplateColumn Header="Rect">
         <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
               <StackPanel x:Name="assTR">
                  <Rectangle HorizontalAlignment="Left" Width="{Binding Koncentracija, Converter={StaticResource StringToDoubleConverter}}" Height="10" Fill="Green" />
               </StackPanel>
            </DataTemplate>
         </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
</DataGrid>

As a general note, you should implement INotifyPropertyChanged in your view model, otherwise changes to the properties at runtime will not be reflected in the user interface.

Related