Change binded null object to some data - c#

problem
I have a 2d array of some objects. For greater flexibility, it is an array of interface types. At the start, some cells can be null, but it fills with data later. I bind this array to UniformGrid in WPF. When I create a new object and add it in the array my grid doesn't change. The question is how to design bindind that if bound objects change (not the properties in object), the grid will change too.
Similar question was there
but without decisions
my code
cells in array
(simple classes with method that can change properties to be sure binding works correctly. nothing special here)
interface IOrg
{
string Text { get; }
SolidColorBrush SolidColor { get; }
void SetColor();
void SetText();
}
class IOrgA : IOrg
{
Random random;
public IOrgA(Random random)
{
SolidColor = new SolidColorBrush();
this.random = random;
SetColor();
SetText();
}
public string Text { get; private set; }
public SolidColorBrush SolidColor { get; private set; }
public void SetColor()
{
SolidColor.Color = Color.FromRgb(255, 255, (byte)random.Next(256));
}
public void SetText()
{
Text = random.Next(1000).ToString();
}
}
class IOrgB : IOrg
{
Random random;
public IOrgB(Random random)
{
SolidColor = new SolidColorBrush();
this.random = random;
SetColor();
SetText();
}
public string Text { get; private set; }
public SolidColorBrush SolidColor { get; private set; }
public void SetColor()
{
SolidColor.Color = Color.FromRgb((byte)random.Next(256), 255, 255);
}
public void SetText()
{
Text = random.Next(1000, 2000).ToString();
}
}
main class
class Table
{
//array with data
private IOrg[,] _layer;
private Random _random = new Random();
public Table(int heigth, int width)
{
Height = heigth;
Widht = width;
_layer = new IOrg[heigth, width];
ListLayer = new List<List<IOrg>>();
FillLayer();
FillList();
}
public int Height { get; private set; }
public int Widht { get; private set; }
//list for binding
public List<List<IOrg>> ListLayer { get; private set; }
void FillLayer()
{
for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Widht; j++)
{
//randomly fill array
if (_random.Next(30) == 1)
{
IOrg org;
if (_random.Next(2) == 0)
{
org = new IOrgA(_random);
}
else
{
org = new IOrgB(_random);
}
_layer[i, j] = org;
}
}
}
}
}
xaml
<Window.Resources>
<!-- size of uniform gris -->
<sys:Int32 x:Key="GridSize" >50</sys:Int32>
<!-- template for each row -->
<DataTemplate x:Key="RowDataTemplate">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource ElementDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="RowGrid" Columns="{StaticResource GridSize}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
<!-- template for each element in row -->
<DataTemplate x:Key="ElementDataTemplate">
<TextBlock Text="{Binding Text, FallbackValue=__}">
<TextBlock.Background>
<SolidColorBrush Color="{Binding SolidColor.Color, FallbackValue=#123456}"></SolidColorBrush>
</TextBlock.Background>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="14*" />
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" x:Name="lst" ItemTemplate="{DynamicResource RowDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="World" Rows="{StaticResource GridSize}" Background="Bisque"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Grid.Row="1" Width="100" Height="50" Click="Button_Click">
button
</Button>
</Grid>
binding
table = new Table(50, 50);
lst.ItemsSource = table.ListLayer;
I randomly fill my array with data, bind it in UniformGrid, and provide FallbackValue for null value objects. When I add a new object to the array, nothing changes. How do Ifix this problem?

Instead of using List as below, try with an ObservableCollection instead.
public List<List<IOrg>> ListLayer { get; private set; }
At any point, if you need to add an object to the collection, just raise the PropertyChanged event for the INotifyPropertyChanged interface which your ViewModel(DataContext) class needs to implement.

Related

Binding data with WPF/C#

I have a problem with data binding (binding update?). After launching the application, I load the images and want to display their histograms on a scale of 0-255. The variable 'frequency' already has the data ready. And I want to bind them to xaml to show some kind of chart. XAML code works well with some static data, but assigning value to 'frequency' it doesn't show anything.
XAML:
<ItemsControl Name="ItemsControlName" ItemsSource="{Binding ChartValues}">
<ItemsControl.DataContext>
<local:DicomChart/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Height="{Binding }" Width="4" VerticalAlignment="Bottom"
Fill="#eee" Stroke="Gray" Margin="-1.0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
private void RegionGrowing(object sender, RoutedEventArgs e)
{
int[] frequency = new int[256];
for (int m = 0; m < 512; m++)
{
for (int n = 0; n < 512; n++)
{
frequency[database[pictureIndex, m, n]] += 1;
}
}
DicomChart dc = new DicomChart();
dc.ChartValues = frequency.ToArray();
}
DicomClass class:
public class DicomChart
{
public DicomChart()
{
}
public int[] ChartValues { get; set; }
}
Try use additional class like DicomChartItem and fill observable collection with it, something like that
C#:
public class DicomChart
{
public ObservableCollection<DicomChartItem> ChartItems{ get; set; } = new ObservableCollection<DicomChartItem>();
}
public class DicomChartItem
{
public int _Value { get; set; }
public DicomChartItem(int Value)
{
_Value = Value;
}
}
XAML:
<ItemsControl ItemsSource="{Binding ChartItems}">
<ItemsControl.DataContext>
<local:DicomChart/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DicomChartItem}">
<Rectangle Height="{Binding _Value}" Width="4" VerticalAlignment="Bottom"
Fill="#eee" Stroke="Gray" Margin="-1.0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Observable collection must be filled in the loop for calling CollectionChanged event, in another case you need to use PropertyChanged event from INotifyPropertyChanged interface.
C#:
private void RegionGrowing(object sender, RoutedEventArgs e)
{
int[] frequency = new int[256];
for (int m = 0; m < 512; m++)
{
for (int n = 0; n < 512; n++)
{
frequency[database[pictureIndex, m, n]] += 1;
}
}
DicomChart dc = new DicomChart();
foreach (var item in frequency)
dc.ChartItems.Add(new DicomChartItem(item));
}
You have to replace int[] with ObservableCollection<int> like I said. It's not going to work with a simple array because UI isn't notified about changes.
Add x:Name="Items" to your ItemsControl in order to be able to access it from the code behind;
Add this code in your RegionGrowing: var ds = (DicomChart) Items.DataContext;
Add values to your ds object like ds.ChartValues.Add(<your_value>).

How can I get nice animations in a ListView when an ObservableCollection gets sorted on UWP?

I cant get nice reorder animation in ListView - also tried overriding all the various internal transitions via the <Transitions><TransitionsCollection>... property. Nothing. Tried using simple .Insert/.Remove and .Move and neither works nicely.
Insert/Remove has animation for the items that happen to have been pushed around and .Move acts like a Reset on the collection.
The following code in MainPage:
public sealed partial class MainPage : Page
{
private ObservableCollection<Holder> Items = new ObservableCollection<Holder>(Enumerable.Range(0, 10).Select(x => new Holder(x)));
public MainPage()
{
this.InitializeComponent();
Items.CollectionChanged += Items_CollectionChanged;
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
private Random _random = new Random();
private bool asc = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
IList<Holder> temp;
//var temp = Items.Skip(1).Union(Items.Take(1)).ToList();
// var temp = new Holder[] { Items.ElementAt(Items.Count - 1) }.Union(Items.Take(Items.Count - 1)).ToList();
temp = Items.OrderBy(x => _random.NextDouble()).ToList();
// temp = asc ? Items.OrderBy(x => x.I).ToList() : Items.OrderByDescending(x => x.I).ToList();
asc = !asc;
for (int i = 0; i < temp.Count; i++)
{
if (Items[i] != temp[i])
{
// When using Move, no animation occurs at all. When using Remove/Insert, there's partial
// animation for the items that happened to stay at the same place.
//Items.Move(Items.IndexOf(temp[i]), i);
Items.Remove(temp[i]);
Items.Insert(i, new Holder(temp[i].I));
}
}
}
}
public class Holder
{
public Holder(int i)
{
I = i;
}
public string Name => "Hey hey hey " + I.ToString();
public int I { get; private set; }
public override bool Equals(object obj)
{
return ((Holder)obj).I == I;
}
public override int GetHashCode()
{
return I.GetHashCode();
}
}
With the following XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Content="Mix" Click="Button_Click" Grid.Row="1"></Button>
<ListView ItemsSource="{x:Bind Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderThickness="3" BorderBrush="Blue">
<TextBlock Text="{Binding Name}" Margin="20"/>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

How to display a Dictionary<string, Dictionary<string, CustomType>> in a Grid using DataTemplate?

I have text entries which have both a type and a category. To store them I use a Dictionary<string, Dictionary<string, ModelEntry>> ModelEntries. Not the prettiest thing I know but works perfectly as a 2-dimensional array with string indexes. In this way for instance I can call ModelEntries["type1"]["category2"].Content to get the text for the corresponding entry. The number of categories and types is fixed to 3 and 4.
How can I display them in a table-like Grid by using a DataTemplate?
My goal is something like this:
where the boxes of the grid would be filled with the content of the corresponding entry.
I've gotten so far to this:
<StackPanel Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="4" Grid.RowSpan="3">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RichTextBox></RichTextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
And I don't know how to continue. Any help would be appreciated.
Side note: I know that since the rows and columns are fixed number I can do what I want with just more code and statically define 12 RichTextBoxes. The reason I want a DataTemplate approach is that there will be interaction with the other fields when a user clicks on a text. Browsing a Dictionary will be times easier than manually checking all statically defined RichTextBoxes.
One of the more common mistakes made when doing WPF development is that people try to fit their view to their existing data model when it really should be the job of an intermediate layer (i.e. a view model) to mediate between the two. XAML is extremely powerful and can do some clever things but if you find yourself having to do too much work in the view or over-relying on things like converters to bend your data into a more useful form then it's usually a good indication that your view model isn't doing its job properly.
Take a step back and look at it from the other direction: you have a view in mind, what type of data do you need to allow that view to work? First of all you're displaying two things: your headers (at the top and left) and your cells, so start by creating classes in your view model for those:
public class CustomGridHeader
{
public int Column { get; set; }
public int Row { get; set; }
public string Header { get; set; }
}
public class CustomGridCell
{
public int Column { get; set; }
public int Row { get; set; }
public object Content { get; set; }
}
Now your view model is going to need a collection of things to display (both headers and cells) and a function to reference the data in your dictionary. I'll leave the translation step to you but here's enough to get started:
private int _NumColumns;
public int NumColumns
{
get { return this._NumColumns; }
set { this._NumColumns = value; RaisePropertyChanged(() => this.NumColumns); }
}
private int _NumRows;
public int NumRows
{
get { return this._NumRows; }
set { this._NumRows = value; RaisePropertyChanged(() => this.NumRows); }
}
private object[] _GridData;
public object[] GridData
{
get { return this._GridData; }
set { this._GridData = value; RaisePropertyChanged(() => this.GridData); }
}
private string[] Types = new string[] { "Type1", "Type2", "Type3" };
private string[] Categories = new string[] { "Cat1", "Cat2", "Cat3" };
private void UpdateGridData()
{
this.NumColumns = this.Categories.Length + 1;
this.NumRows = this.Types.Length + 1;
this.GridData = new object[(this.Categories.Length + 1) * (this.Types.Length + 1)];
// set category and type headers
for (int i=0; i<this.Categories.Length; i++)
this.GridData[i + 1] = new CustomGridHeader { Column = i + 1, Row = 0, Header = this.Categories[i] };
for (int i=0; i<this.Types.Length; i++)
this.GridData[(i + 1) * this.NumColumns] = new CustomGridHeader { Column = 0, Row = i + 1, Header = this.Types[i] };
// fill the cells
for (int i = 0; i < this.Categories.Length; i++)
for (int j = 0; j < this.Types.Length; j++)
this.GridData[(i + 1) * this.NumColumns + j + 1] = new CustomGridCell {Column = i+1, Row = j+1, Content = String.Format("{0}/{1}", i, j)};
}
Code-wise that's all you need, you now have your data in a form ready to be consumed by your view. You want to display your data in a grid, but functionally what you're really doing is displaying a list of items, so what you really should be doing is using an ItemsControl but specifying a Grid as the Panel Template. You'll also need to set the ItemContainerStyle to set the grid column and row for each element. Finally you'll need two data templates: one that specifies how your headers should be displayed and another to specify how the cells should be displayed:
<ItemsControl ItemsSource="{Binding GridData}" Margin="50">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:CustomGridHeader}">
<TextBlock Text="{Binding Header}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomGridCell}">
<Border BorderBrush="Black" BorderThickness="1" Margin="0,0,-1,-1">
<TextBlock Text="{Binding Content}" Background="AliceBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="50"/>
</Border>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Behaviors>
<local:GridSizeBehavior Columns="4" Rows="4"/>
</i:Interaction.Behaviors>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding Column}" />
<Setter Property="Grid.Row" Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The only missing piece of the puzzle is GridSizeBehavior. The Grid control doesn't have "NumColumns" or "NumRows" fields, but you can effectively add it yourself with an attached behaviour (for this and other reasons I'm not convinced that Grid is the best control for this task but that's what you've asked for so that's what I'm providing):
public class GridSizeBehavior : Behavior<Grid>
{
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnColumnsChanged));
static void OnColumnsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var behaviour = depObj as GridSizeBehavior;
if (behaviour == null)
return;
behaviour.SetColumns();
}
private void SetColumns()
{
var grid = this.AssociatedObject;
if (grid == null)
return;
int columns = this.Columns;
grid.ColumnDefinitions.Clear();
for (int i = 0; i < columns; i++)
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
}
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
// Using a DependencyProperty as the backing store for Rows. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnRowsChanged));
static void OnRowsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var behaviour = depObj as GridSizeBehavior;
if (behaviour == null)
return;
behaviour.SetRows();
}
private void SetRows()
{
var grid = this.AssociatedObject;
if (grid == null)
return;
int Rows = this.Rows;
grid.RowDefinitions.Clear();
for (int i = 0; i < Rows; i++)
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
}
protected override void OnAttached()
{
base.OnAttached();
SetColumns();
SetRows();
}
}
Put all those together and you'll get the result you're after:
The main downside to the exact implementation shown here is that the view won't update automatically in response to changes to your original dictionaries, but given that Dictionary doesn't support collection change notification that was never going to happen anyway. Yes, you could have done all this entirely in your view using converters to enumerate your columns and rows and bind to your dictionary data directly, but at the end of the day you would have found that your code would have pretty much matched what I've done here in the view model anyway, so you may as well do it there in a form that's easy to debug and unit test.

Using Custom Controls, with dependency properties in an Itemscontrol datatemplate

I am trying to create a polar diagram using MVVM. The radial axis is wind speed and radial angles is wind direction. I can create the radial circles (ellipses) and the radial speed axis labels. For the angular lines (like wheel spokes) I created a custom control that was simply a line with a textblock at the end (giving the angular axis value). However, I can not get my program to set the properties of this custom control and therefore you cannot see them on the canvas (though they are there but the default length is zero and default text is null)?
OK, So my diagram.xaml is as follows:
<Grid>
<Grid.DataContext>
<local:DiagramViewModel x:Name="ViewModel" />
</Grid.DataContext>
<Viewbox x:Name="myView"
Stretch="Uniform"
StretchDirection="Both">
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="400" Width="400"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Right" Value="{Binding Right}"/>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Bottom}"/>
<Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:EllipseItem}">
<Ellipse Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
StrokeDashArray="{Binding StrokeDashArray}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:GridLineItem}">
<View:GridLineComponent Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Length="{Binding Path=Length, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
GridHeight="{Binding Path=GridHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LineColour="{Binding Path=LineColour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextColour="{Binding Path=TextColour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RenderTransform="{Binding RenderTransform}"
RenderTransformOrigin="{Binding RenderTransformOrigin}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TextBlockItem}">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontSize="{Binding FontSize}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Viewbox>
</Grid>
My viewmodel (DiagramViewModel), as well as other things, contains the following method to create these lines:
private void CreateAngularLines(double originMargin)
{
IList<GridLineItem> angularLine = new List<GridLineItem>();
double increment , angularGridlineCount, angle, height, width;
if (this.minorAngularIncrement == null)
increment = (double)this.majorAngularIncrement;
else
increment = (double)this.minorAngularIncrement;
angularGridlineCount = 360 / increment;
height = 200;//half the length of the canvas
width = 200;
for (int i = 0; i < angularGridlineCount; i++)
{
angularLine.Add(new GridLineItem());
angle = i * increment;
if (angle == 0 || angle == 180)
angularLine[i].Text = angle.ToString("000");
else if (angle < 180)
angularLine[i].Text = "G" + angle.ToString("000");
else
angularLine[i].Text = "R" + (360 - angle).ToString("000");
angularLine[i].Length = height - originMargin;
angularLine[i].GridHeight = height;
angularLine[i].LineColour = this.LineColour;
angularLine[i].TextColour = this.TextColour;
angularLine[i].Left = width - 18;
angularLine[i].Top = 0;
angularLine[i].Right = width;
angularLine[i].Bottom = height;
angularLine[i].ZIndex = 1;
angularLine[i].RenderTransformOrigin = new Point(0.5, 1);
angularLine[i].RenderTransform = new RotateTransform(angle, 0, 0);
}
Points.Add(new CollectionContainer() { Collection = angularLine });
}
The GridLineItem class is a simple holder class with the following:
public class GridLineItem
{
public GridLineItem()
{
this.RenderTransformOrigin = new Point(0.5, 1);
this.RenderTransform = new RotateTransform(0, 0, 0);
this.Left = double.NaN;
this.Right = double.NaN;
this.Top = double.NaN;
this.Bottom = double.NaN;
}
public Brush LineColour { get; set; }
public Brush TextColour { get; set; }
public string Text { get; set; }
public double Length { get; set; }
public double GridHeight { get; set; }
public Point RenderTransformOrigin { get; set; }
public Transform RenderTransform { get; set; }
public int ZIndex { get; set; }
public double Left { get; set; }
public double Right { get; set; }
public double Top { get; set; }
public double Bottom { get; set; }
}
Now this is where I think I am going wrong. The GridlineComponent.xaml.cs file contains the following:
public partial class GridLineComponent : UserControl, INotifyPropertyChanged
{
public GridLineComponent()
{
InitializeComponent();
this.DataContext = this;
}
public GridLineComponent(string text, double length, double gridHeight) : this()
{
this.Text = text;
this.Length = length;
this.GridHeight = gridHeight;
}
public Brush LineColour
{
get { return (Brush)GetValue(LineColourProperty); }
set
{
SetValue(LineColourProperty, value);
NotifyPropertyChanged("LineColour");
}
}
public Brush TextColour
{
get { return (Brush)GetValue(TextColourProperty); }
set
{
SetValue(TextColourProperty, value);
NotifyPropertyChanged("TextColour");
}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set
{
if (value == null)
SetValue(TextProperty, "");
else
SetValue(TextProperty, value); //"\u00B0";
NotifyPropertyChanged("Text");
}
}
public double Length
{
get { return (double)GetValue(LengthProperty); }
set
{
SetValue(LengthProperty, value);
NotifyPropertyChanged("Length");
}
}
public double GridHeight
{
get { return (double)GetValue(GridHeightProperty); }
set
{
SetValue(GridHeightProperty, value);
NotifyPropertyChanged("GridHeight");
}
}
public static readonly DependencyProperty TextColourProperty =
DependencyProperty.Register(
"TextColour",
typeof(Brush),
typeof(GridLineComponent),
new PropertyMetadata(default(Brush), OnItemsPropertyChanged));
public static readonly DependencyProperty LineColourProperty =
DependencyProperty.Register(
"LineColour",
typeof(Brush),
typeof(GridLineComponent),
new PropertyMetadata(default(Brush), OnItemsPropertyChanged));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(GridLineComponent),
new PropertyMetadata(default(string), OnItemsPropertyChanged));
public static readonly DependencyProperty LengthProperty =
DependencyProperty.Register(
"Length",
typeof(double),
typeof(GridLineComponent),
new PropertyMetadata(default(double), OnItemsPropertyChanged));
public static readonly DependencyProperty GridHeightProperty =
DependencyProperty.Register(
"GridHeight",
typeof(double),
typeof(GridLineComponent),
new PropertyMetadata(default(double), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GridLineComponent source = d as GridLineComponent;
// Do something...
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Basically this GridLineComponent is not following the rules of MVVM. But I think that is ok here as it is such a basic type. The xaml for it is simply a grid containing a line and a TextBlock. The line's Y2 and stroke attributes are binded to the xaml.cs (Length and LineColour respectively) and so too are the textblock's foreground and text attributes (TextColour and Text respectively).
From what I have read, these lines should draw but do not. I think it may have something to do with the dependency properties or because of the empty OnItemsPropertyChanged handler. ALso, when the program is run, only the non argument constructor, for the GridlineComponent.xaml.cs is accessed (non of the properties are set)?

DataTemplate binding doesn't work?

I'm write the following code for binding some properties
<StackPanel x:Name="channelsRecordTimeData" Orientation="Vertical">
<ItemsControl x:Name="channelRecordTimeItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="gridChannelRecordTimeItem" Width="{Binding Path=ChannelRecordTimeItemWidth}"
Height="{Binding Path=ChannelRecordTimeItemHeight}" Margin="{Binding Path=ChannelRecordTimeItemsMargin}"
HorizontalAlignment="Left" DataContext="{Binding Path=ListRecordTime}">
<Grid.Background>
<ImageBrush x:Name="gridChannelRecordTimeItemBgr" ImageSource="..\Resources\playback_grid_channel_record_time_item_bgr_normal.png"/>
</Grid.Background>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
public class DATA
{
public double ChannelRecordTimeItemWidth { set; get; }
public double ChannelRecordTimeItemHeight { set; get; }
public Thickness ChannelRecordTimeItemsMargin { set; get; }
public List<RecordTime> ListRecordTime { set; get; }
public DATA()
{
ChannelRecordTimeItemWidth = 1000;
ChannelRecordTimeItemHeight = 20;
ChannelRecordTimeItemsMargin = new System.Windows.Thickness(0, 0, 0, 0);
ListRecordTime = null;
}
}
public static List<DATA> listDATA = new List<DATA>();
for(int i = 0 ; i < 10 ; i++)
{
DATA data = new DATA();
listDATA.Add(data);
}
channelRecordTimeItems.ItemsSource = listDATA;
channelRecordTimeItems.Items.Refresh();
At above code, I have added 10 items in StackPanel, but I don't see any items added when run app.
But when I replace Width="{Binding Path=ChannelRecordTimeItemWidth}" by Width="1000" and replace Height="{Binding Path=ChannelRecordTimeItemHeight}" by Height="20", then it work fine!
I think, this is problem of binding, but I don't know why.
Someone can tell me how to make it work?
Many thanks,
T&T
Update your DATA class to implement INotifyPropertyChanged like so:
public class DATA : : INotifyPropertyChanged
{
private double _channelRecordTimeItemWidth;
private double _channelRecordTimeItemHeight;
private Thickness _channelRecordTimeItemsMargin;
private List<RecordTime> _listRecordTime;
public double ChannelRecordTimeItemWidth
{
get { return _channelRecordTimeItemWidth; }
set
{
_channelRecordTimeItemWidth = value;
OnPropertyChanged("ChannelRecordTimeItemWidth");
}
}
public double ChannelRecordTimeItemHeight
{
get { return _channelRecordTimeItemHeight; }
set
{
_channelRecordTimeItemHeight = value;
OnPropertyChanged("ChannelRecordTimeItemHeight");
}
}
public Thickness ChannelRecordTimeItemsMargin
{
get { return _channelRecordTimeItemsMargin; }
set
{
_channelRecordTimeItemsMargin = value;
OnPropertyChanged("ChannelRecordTimeItemsMargin");
}
}
public List<RecordTime> ListRecordTime
{
get { return _listRecordTime; }
set
{
_listRecordTime = value;
OnPropertyChanged("ListRecordTime");
}
}
public DATA()
{
ChannelRecordTimeItemWidth = 1000;
ChannelRecordTimeItemHeight = 20;
ChannelRecordTimeItemsMargin = new System.Windows.Thickness(0, 0, 0, 0);
ListRecordTime = null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
This will notify the XAML to update the bounded value.
The DataContext should also be set correctly. First remove the bound DataContext of the Grid:
<DataTemplate>
<Grid x:Name="gridChannelRecordTimeItem" Width="{Binding Path=ChannelRecordTimeItemWidth}"
Height="{Binding Path=ChannelRecordTimeItemHeight}" Margin="{Binding Path=ChannelRecordTimeItemsMargin}"
HorizontalAlignment="Left">
<Grid.Background>
<ImageBrush x:Name="gridChannelRecordTimeItemBgr" ImageSource="..\Resources\playback_grid_channel_record_time_item_bgr_normal.png"/>
</Grid.Background>
</Grid>
</DataTemplate>
and make sure that the DataContext for the XAML (whether it is a UserControl, Window, etc), is set to your DATA class.
Your solution cannot work because of this line
DataContext="{Binding Path=ListRecordTime}"
This line sets datacontext for the grid, then you are trying to get ChannelRecordTimeItemHeight from datacontext - list of recordtimes.
Delete this line and see what happens

Categories

Resources