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)?
Related
I am looking for a wrappanel or rather something that works similarly. The wrappanel grabs the Uielement in a new line, but the elements are not dynamic in width, but static. The setting »HorizontalAlignment=" Stretch"« does not work and the elements look squeezed together. That means you have to put the width manually »Width="200"« and that is very static. So when I change the width of my window, more elements come into one line, but the existing elements do not adapt to the width of the window. I would have liked to fill out the elements for the entire width. Overall, it also sees much better.
I made a user control that fits my purpose but it can’t be virtualized and is really slow with the resize.
Here is my code for the adaptable UserControl.
<UserControl x:Class="ItemViewer"
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"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Loaded="UserControl_Loaded"
x:Name="uc">
<Grid DataContext="{Binding ElementName=uc}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"
x:Name="SecondRow" />
</Grid.RowDefinitions>
<TextBlock x:Name="Header"
Text="{Binding HeaderText,Mode=OneWay}"
Foreground="White"
HorizontalAlignment="Left"
Margin="0,0,0,10"
FontSize="28"
FontWeight="Medium" />
<ItemsControl Grid.Row="2"
x:Name="ListViewProducts"
SizeChanged="Grid_SizeChanged"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Items,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding Path=DataContext.ItemHeight, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
MaxWidth="{Binding Path=DataContext.ItemMaxWidth, RelativeSource={RelativeSource AncestorType=ItemsControl},Mode=OneTime}"
Margin="0,0,10,10">
<Border Background="Beige"
CornerRadius="10" />
<!-- The specific UserControl or an element -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"
Initialized="UniformGrid_Initialized" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Top" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
The code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
namespace MVVM.View.HomeView
{
/// <summary>
/// Interaktionslogik für ItemViewer.xaml
/// </summary>
public partial class ItemViewer : UserControl
{
private UniformGrid? _itemUniformGrid;
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ItemViewer));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(ItemViewer));
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public static readonly DependencyProperty ItemMinWidthProperty = DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(ItemViewer));
public double ItemMinWidth { get { return (double)GetValue(ItemMinWidthProperty); } set { SetValue(ItemMinWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(ItemViewer));
public double ItemWidth { get { return (double)GetValue(ItemWidthProperty); } set { SetValue(ItemWidthProperty, value); List_SizeChanged(); } }
public static readonly DependencyProperty ItemMaxWidthProperty = DependencyProperty.Register("ItemMaxWidth", typeof(double), typeof(ItemViewer));
public double ItemMaxWidth { get { return (double)GetValue(ItemMaxWidthProperty); } set { SetValue(ItemMaxWidthProperty, value); List_SizeChanged(); } }
private double row = 0;
public double Row { get { return row; } set { row = (value >= 0 ? value : 0); if (Row != 0) { SecondRow.MaxHeight = 10 * (Row - 1) + ItemHeight * Row; } } }
public ItemViewer()
{
InitializeComponent();
}
private int itemCounter = 0;
public ObservableCollection<object> Items
{
get { return (ObservableCollection<object>)GetValue(ItemsProperty); }
set => SetValue(ItemsProperty, value);
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<object>), typeof(ItemViewer));
private const int WmExitSizeMove = 0x232;
private IntPtr HwndMessageHook(IntPtr wnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WmExitSizeMove:
List_SizeChanged();
handled = true;
break;
}
return IntPtr.Zero;
}
private void List_SizeChanged()
{
if (Items == null || Items.Count == 0 || _itemUniformGrid == null)
return;
itemCounter = Items.Count;
int columns = (int)(ListViewProducts.ActualWidth / (10 + ItemWidth));
if (columns <= itemCounter && ListViewProducts.ActualWidth < (ItemMaxWidth + 10) * columns)
{
if (_itemUniformGrid.HorizontalAlignment != HorizontalAlignment.Stretch)
{
_itemUniformGrid.ClearValue(WidthProperty);
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
}
if (columns != _itemUniformGrid.Columns)
_itemUniformGrid.Columns = columns;
}
else
{
if (columns >= itemCounter)
{
double newWidth;
newWidth = ListViewProducts.ActualWidth / itemCounter;
newWidth = newWidth > ItemMaxWidth ? ItemMaxWidth : newWidth;
_itemUniformGrid.Width = (newWidth + 10) * itemCounter;
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = itemCounter;
}
else
{
_itemUniformGrid.HorizontalAlignment = HorizontalAlignment.Left;
_itemUniformGrid.Columns = (int)(ListViewProducts.ActualWidth / (ItemMaxWidth + 10));
_itemUniformGrid.Width = (ItemMaxWidth + 10) * _itemUniformGrid.Columns;
}
}
}
private void UniformGrid_Initialized(object sender, EventArgs e) =>
_itemUniformGrid = (UniformGrid)sender;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
List_SizeChanged();
if (Application.Current.MainWindow == null)
return;
WindowInteropHelper? helper = new(Application.Current.MainWindow);
HwndSource? source = HwndSource.FromHwnd(helper.Handle);
if (source != null)
source.AddHook(HwndMessageHook);
}
private int sizeP = 0;
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sizeP - e.NewSize.Width is < (-200) or > 200)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
else if (Application.Current.MainWindow.WindowState == WindowState.Maximized && sizeP != (int)e.NewSize.Width)
{
sizeP = (int)e.NewSize.Width;
List_SizeChanged();
}
}
}
}
Implementation:
<local:ItemViewer Items="{Binding Objects}"
HeaderText="Recent"
ItemMinWidth="200"
ItemWidth="350"
ItemHeight="150"
ItemMaxWidth="500"
Row="1" />
To make it more responsive I call the resized only when the resize finished. I think it doesn't break with mvvm because it is only for the UI. But in the end, it is quite a bad implementation.
So, is there a better way or good code on GitHub that has a similar behaviour? Here are a few Images of an implementation of my code:
The Width is set dynamically and it wraps when there is enough space.
Here my implementation of my UserControl. The Broders have a dynamic width and also can wrap, but is realy slow.
Here the same with a WrapPanel. Static width and fast wrapping
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.
I have a sample program to recreate the issue I am having.
Control:
public class PropertyUpdateControl : Control
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(double), typeof(PropertyUpdateControl),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ValueProperty_Changed));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void ValueProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lPropertyUpdateControl = (PropertyUpdateControl) d;
lPropertyUpdateControl.Value_Changed((double)e.OldValue, (double)e.NewValue);
}
private void Value_Changed(double oldValue, double newValue)
{
this.Value = newValue;
this.Label = newValue.ToString();
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(Label), typeof(string), typeof(PropertyUpdateControl), new PropertyMetadata("Label"));
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
}
Main Window
public class MainWindowViewModel : ViewModelBase
{
private PropertyUpdateViewModel mPropertyUpdateViewModel;
public MainWindowViewModel()
{
this.PropertyUpdateViewModel = new PropertyUpdateViewModel();
this.ChangeCommand = new RelayCommand(this.ChangeImplementation);
}
public ICommand ChangeCommand { get; set; }
public void ChangeImplementation()
{
if (this.PropertyUpdateViewModel == null)
{
this.PropertyUpdateViewModel = new PropertyUpdateViewModel();
}
else
{
this.PropertyUpdateViewModel = null;
}
}
public PropertyUpdateViewModel PropertyUpdateViewModel
{
get { return this.mPropertyUpdateViewModel;}
set { this.Set(ref this.mPropertyUpdateViewModel, value); }
}
}
public class PropertyUpdateViewModel : ViewModelBase
{
private double mValue;
private static int sInstanceCounter = 0;
public PropertyUpdateViewModel()
{
Interlocked.Increment(ref sInstanceCounter);
this.Value = sInstanceCounter;
}
public double Value
{
get { return this.mValue; }
set { this.Set(ref this.mValue, value); }
}
}
XAML
<Window.Resources>
<local:MainWindowViewModel x:Key="mKeyMainWindowViewModel" />
<Style TargetType="{x:Type local:PropertyUpdateControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PropertyUpdateControl}">
<TextBlock
Margin="25" Text="{TemplateBinding Label}"></TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="mDataTemplate" DataType="{x:Type local:PropertyUpdateViewModel}">
<local:PropertyUpdateControl
Value="{Binding Value}"></local:PropertyUpdateControl>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<StaticResource ResourceKey="mKeyMainWindowViewModel"/>
</Window.DataContext>
<Grid>
<StackPanel>
<ContentPresenter Content="{Binding PropertyUpdateViewModel}" ContentTemplate="{StaticResource mDataTemplate}"/>
<Button Content="Update Value" Command="{Binding ChangeCommand}"></Button>
</StackPanel>
</Grid>
So basically the control seems to only be updated once. The label will always read 1 even after setting the view model to null and then to a new instance of the PropertyUpdateViewModel which will have an incrementing value. Why is the PropertyUpdateControl Value_Changed only called once and never called again even after the datacontext changes?
I found the issue to be in
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(double), typeof(PropertyUpdateControl),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ValueProperty_Changed));
It needs to be FrameworkPropertyMetadataOptions.BindsTwoWayByDefault.
This post has more info on binding for anyone interested https://www.tutorialspoint.com/wpf/wpf_data_binding.htm
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
I'm trying to position elements in my Canvas relative to my background.
Window is re-sized keeping the aspect ratio.
Background is stretched with window size.
The problem is once window is re-sized the element positions are incorrect. If window is re-sized just a little, elements will adjust their size a bit and would be still in the correct position, but if window is re-sized to double it's size then positioning is completely off.
So far I used Grid, but it was to no avail as well. Here is the XAML
<Window x:Class="CanvasTEMP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" ResizeMode="CanResizeWithGrip" SizeToContent="WidthAndHeight" MinHeight="386" MinWidth="397.5" Name="MainWindow1"
xmlns:c="clr-namespace:CanvasTEMP" Loaded="onLoad" WindowStartupLocation="CenterScreen" Height="386" Width="397.5" WindowStyle="None" AllowsTransparency="True" Topmost="True" Opacity="0.65">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Height="77" Width="218">
<Label Content="{Binding OwnerData.OwnerName}" Height="36" Canvas.Left="8" Canvas.Top="55" Width="198" Padding="0" HorizontalAlignment="Left" HorizontalContentAlignment="Center" VerticalAlignment="Center"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.Background>
<ImageBrush ImageSource="Resources\default_mapping.png" Stretch="Uniform"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding OwnerData.left}" />
<Setter Property="Canvas.Top" Value="{Binding OwnerData.top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Class that is used for data binding
public class Owner : INotifyPropertyChanged
{
public double _left;
public double _top;
public string OwnerName { get; set; }
public double top { get { return _top; }
set
{
if (value != _top)
{
_top = value;
OnPropertyChanged();
}
}
}
public double left
{
get { return _left; }
set
{
if (value != _left)
{
_left = value;
OnPropertyChanged();
}
}
}
public string icon { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ForDisplay
{
public Owner OwnerData { get; set; }
public int Credit { get; set; }
}
And here is the code that is run every second to keep elements' position relative to window size
items[0].OwnerData.left = this.Width * (10 / Defaul_WindowSize_Width);
items[0].OwnerData.top = this.Height * (55 / Defaul_WindowSize_Height);
10 and 50 are default Canvas.Left and Canvas.Top that are used when window is first initialized.
Would appreciate if anyone can point out what I'm doing wrong.
You need an attach property:
public static readonly DependencyProperty RelativeProperty =
DependencyProperty.RegisterAttached("Relative", typeof(double), typeof(MyControl));
public static double GetRelative(DependencyObject o)
{
return (double)o.GetValue(RelativeProperty);
}
public static void SetRelative(DependencyObject o, double value)
{
o.SetValue(RelativeProperty, value);
}
and a converter:
public class RelativePositionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var rel = (double)values[0];
var width = (double)values[1];
return rel * width;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and when you add child to Canvas you need like this:
var child = new Child();
SetRelative(child, currentPosition / ActualWidth);
var multiBinding = new MultiBinding { Converter = new RelativePositionConverter() };
multiBinding.Bindings.Add(new Binding { Source = child, Path = new PropertyPath(RelativeProperty) });
multiBinding.Bindings.Add(new Binding { Source = canvas, Path = new PropertyPath(ActualWidthProperty) });
BindingOperations.SetBinding(child, LeftProperty, multiBinding);
Children.Add(child);
If you need, you can change Relative value of child separate of Canvas