WPF Binding GridView to Element in collection - c#

I'm creating a form that will allows user to add filters to data for processing.
I have setup:
public Class RuleGroup
{
public ObservableCollection<Rule> Rules {get; set;}
...
}
public Class Rule
{
public ObservableCollection<String> Fields {get; set;}
public ObservableCollection<Rule> Rules {get; set;}
...
}
public class Criteria
{
public int ItemId{ get; set;}
public string Field{ get; set;}
public OperationType Operation{ get; set;}
public string Value {get; set;}
public string Value2 {get; set;}
}
So a Rule has a List of Criteria that must be matched if the rule is to be applied. Each Criteria in a Rule must specify a value for every field selected. The Amount of fields may vary from One RuleGroup to the next.
I am trying to set up a form that is user friendly when creating multiple Rules. I was thinking of having a GridView on the form that is some how bound to this class layout.
Rule = Row
Criteria = Column
Currently I have function that generates a DataTable based on the Rules/Criteria as the user move from one RuleGroup to the next, but I think there my be an nicer solution to this
Any ideas or help would be much appreciated
Thanks

Right Think I have got it.
Needed to change my Classes around a bit to get the correct groups / hierarchy. I have then been able to bind the column using the items index in the collection.
This has given me the outcome I wanted, Though there is a minor issue where I would like to be able to access the index using the string Name rather then the position. I am currently having to make sure that the "Criterion" are in the correct order when accessing the values.
Here is a link to the Source code
Rule Group
public class RuleGroup
{
public String Description { get; set; }
public ObservableCollection<Rule> Rules { get; set; }
public RuleGroup()
{
Rules = new ObservableCollection<Rule>();
}
}
Rule
public class Rule
{
public Rule()
{
Criteria = new ObservableCollection<Criteria>();
}
public String Description { get; set; }
public ObservableCollection<Criteria> Criteria { get; set; }
readonly ObservableCollection<RuleField> _Fields = new ObservableCollection<RuleField>();
public IEnumerable<RuleField> Fields
{
get
{
return _Fields;
}
}
public void AddField(string name, string header)
{
if (_Fields.FirstOrDefault(i => i.Name == name) == null)
{
RuleField field = new RuleField(_Fields.Count)
{
Name = name,
Header = header
};
_Fields.Add(field);
AddFieldToCriteria(field);
}
}
void AddFieldToCriteria(RuleField field)
{
foreach (Criteria c in Criteria)
{
if (c.Values.FirstOrDefault(i => i.Field == field) == null)
c.Values.Add(new Criterion() { Field = field, Operation = 1 });
}
}
}
Criteria
public class Criteria
{
public Criteria()
{
Values = new ObservableCollection<Criterion>();
}
public ObservableCollection<Criterion> Values { get; set; }
public Criterion this[int index]
{
get
{
return Values.OrderBy(i=>i.Field.Position).ElementAt(index);
}
set
{
Criterion c = Values.OrderBy(i => i.Field.Position).ElementAt(index);
c= value;
}
}
}
Criterion
public class Criterion
{
public RuleField Field { get; set; }
public int Operation { get; set; }
public object Value { get; set; }
public object Value2 { get; set; }
}
RuleField
public class RuleField
{
public string Name { get; set; }
public string Header { get; set; }
int _Position = 0;
public int Position
{
get
{
return _Position;
}
}
public RuleField(int position)
{
_Position = position;
}
}
View Model
public delegate void UpdateColumnsEventHandler(object sender, UpdateColumnsEventArgs e);
public class UpdateColumnsEventArgs
{
public IEnumerable<RuleField> Columns { get; set; }
public UpdateColumnsEventArgs()
{
Columns = new List<RuleField>();
}
public UpdateColumnsEventArgs(IEnumerable<RuleField> columns)
{
Columns = columns;
}
}
public class MainWindowViewModel
{
public event UpdateColumnsEventHandler UpdateColumns;
public ObservableCollection<RuleGroup> RuleGroups { get; set; }
RuleGroup _SelectedRuleGroup = null;
public RuleGroup SelectedRuleGroup
{
get
{
return _SelectedRuleGroup;
}
set
{
if (_SelectedRuleGroup == value)
return;
_SelectedRuleGroup = value;
}
}
public Rule _SelectedRule = null;
public Rule SelectedRule
{
get
{
return _SelectedRule;
}
set
{
if (_SelectedRule == value)
return;
_SelectedRule = value;
if (UpdateColumns != null)
UpdateColumns(this, new UpdateColumnsEventArgs(_SelectedRule.Fields));
}
}
public MainWindowViewModel()
{
RuleGroups = new ObservableCollection<RuleGroup>();
RuleGroup rg = new RuleGroup();
rg.Description = "Rule Group A";
Rule r = new Rule();
r.Description = "Rule 1";
Random random = new Random();
int range = 10000;
for (int x = 0; x < 2000; x++)
{
Criteria c = new Criteria();
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "A",
Header = "A Column"
},
Operation = 1,
Value = "X"
});
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "B",
Header = "B Column"
},
Operation = 1,
Value = x % 10
});
r.Criteria.Add(c);
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
r.AddField("C", "C Column");
#endregion
rg.Rules.Add(r);
r = new Rule();
r.Description = "Rule 2";
for (int x = 0; x < 1750; x++)
{
r.Criteria.Add(new Criteria());
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
#endregion
rg.Rules.Add(r);
RuleGroups.Add(rg);
}
}
WPF Window
<Window x:Class="RuleMappingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:RuleMappingTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel UpdateColumns="UpdateGridColumns"/>
</Window.DataContext>
<Grid Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding RuleGroups}" SelectedItem="{Binding SelectedRuleGroup}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Row="1" ItemsSource="{Binding SelectedRuleGroup.Rules}" SelectedItem="{Binding SelectedRule}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DataGrid x:Name="CriteriaGrid" Grid.Row="2" ItemsSource="{Binding SelectedRule.Criteria}" AutoGenerateColumns="False" >
</DataGrid>
</Grid>
</Window>
WPF Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void UpdateGridColumns(object sender, UpdateColumnsEventArgs e)
{
CriteriaGrid.Columns.Clear();
foreach (RuleField rf in e.Columns)
{
DataGridTextColumn c = new DataGridTextColumn();
c.Header = rf.Header;
Binding b = new Binding(String.Format("[{0}].Value", rf.Position));
CriteriaGrid.Columns.Add(c);
c.Binding = b;
}
}
}

Related

WPF data binding, on properties values changed the UI don't refresh its content [duplicate]

This question already has answers here:
INotifyPropertyChanged WPF
(3 answers)
Closed 1 year ago.
I know there is a lot of questions that can look like this, but i don't realy find anyone answer to my problem here and in another forums.
So, I'm relatively new to WPF and I'm testing data binding, but I'm getting a trouble that data don't get values updated when values of a ObservableCollection get changed.
I will put my exemple were.
Main.xaml
<Window x:Class="TestWPF.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:TestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="MyGrid">
</Grid>
</Window>
Main.xaml.cs
using System.Windows;
namespace TestWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Stand stand = new Stand("Best Seller Stand");
stand.cars.Add(new Car()
{
ID = "1",
Brand = "BMW",
CarNumber = 165,
HaveRadio = true
});
stand.cars.Add(new Car()
{
ID = "2",
Brand = "Toyota",
CarNumber = 421,
HaveRadio = true
});
stand.cars.Add(new Car()
{
ID = "4",
Brand = "FIAT",
CarNumber = 312,
HaveRadio = false
});
stand.cars.Add(new Car()
{
ID = "3",
Brand = "Ferrari",
CarNumber = 12,
HaveRadio = true
});
MyGrid.Children.Add(stand.GetCatalog());
}
}
}
Car.cs
using System;
namespace TestWPF
{
public class Car : IComparable, IComparable<int>
{
public string ID { get; set; }
public string Brand { get; set; }
public int CarNumber { get; set; }
public bool HaveRadio { get; set; }
public void GerateRandomCarNumber()
{
CarNumber = new Random().Next(int.MinValue, int.MaxValue);
}
public int CompareTo(int other)
{
return CarNumber.CompareTo(other);
}
public int CompareTo(object obj)
{
Car other = null;
if (obj is Car)
other = obj as Car;
return CarNumber.CompareTo(other.CarNumber);
}
}
}
Stand.cs
using System.Collections.Generic;
using System.Linq;
namespace TestWPF
{
public class Stand
{
public Stand(string name)
{
Name = name;
}
public string Name { get; set; }
public SortedSet<Car> cars { get; set; } = new SortedSet<Car>();
public Car BestChoice
{
get
{
return cars.First();
}
}
public StandCatalog Catalog { get; set; } = null;
public StandCatalog GetCatalog()
{
if (Catalog == null)
Catalog = new StandCatalog(this);
return Catalog;
}
}
}
StandCatalog.xaml (UserControl)
<UserControl x:Class="TestWPF.StandCatalog"
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:TestWPF"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
<Label Name="StandName" Content="{Binding Model.Name}" Margin="10"/>
<Label Name="CarBrand" Content="{Binding Model.BestChoice.Brand}" Margin="10"/>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding CatalogCar}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Brand" Binding="{Binding Brand}"/>
<DataGridTextColumn Header="Car Number" Binding="{Binding CarNumber}"/>
<DataGridCheckBoxColumn Header="Have Radio" Binding="{Binding HaveRadio}"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="Gerate Random Number" Click="btn_GerateRandomNumber"/>
</StackPanel>
</UserControl>
StandCatalog.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace TestWPF
{
/// <summary>
/// Interaction logic for StandCatalog.xaml
/// </summary>
public partial class StandCatalog : UserControl
{
public Stand Model { get; init; }
public ObservableCollection<Car> CatalogCar { get; set; }
public StandCatalog()
{
InitializeComponent();
this.DataContext = this;
}
public StandCatalog(Stand model) : this()
{
Model = model;
CatalogCar = new ObservableCollection<Car>(Model.cars);
}
private void btn_GerateRandomNumber(object sender, RoutedEventArgs e)
{
foreach (var item in Model.cars)
{
item.GerateRandomCarNumber();
}
}
}
}
So I get this aplication:
But when I click on the button to gerate random number, the datagrid don't refresh and the label (Name="CarBrand") don't change either...
Doesn't data binding refresh the UI when the elements changed its value?
I know that the value changed because when i reorder the datagrid I get this:
Can anyone help me?
Another question, I'm using the class Stand as a Model of the StandCatalog (view/controller), what is the best way to use the SortedSet and the ObservableCollection together? Or should I use a SortedSet in the model?
Option 1: INotifyPropertyChanged
The Car class should implement the INotifyPropertyChanged interface to inform targets when a property changes.
public class Car : IComparable, IComparable<int>, INotifyPropertyChanged
{
private int _carNumber;
public string ID { get; set; }
public string Brand { get; set; }
public int CarNumber
{
get => _carNumber;
set
{
if (_carNumber == value) return;
_carNumber = value;
OnPropertyChanged();
}
}
public bool HaveRadio { get; set; }
public void GerateRandomCarNumber() { CarNumber = new Random().Next(int.MinValue, int.MaxValue); }
public int CompareTo(int other) { return CarNumber.CompareTo(other); }
public int CompareTo(object obj)
{
Car other = null;
if (obj is Car)
other = obj as Car;
return CarNumber.CompareTo(other.CarNumber);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
}
Option 2: DependencyProperty
Define CarNumber property as a DependencyProperty. The infrastructure will handle the changes.
public class Car : DependencyObject, IComparable, IComparable<int>
{
public static readonly DependencyProperty
CarNumberProperty = DependencyProperty.Register("CarNumber", typeof(int), typeof(Car));
public string ID { get; set; }
public string Brand { get; set; }
public int CarNumber
{
get => (int)GetValue(CarNumberProperty);
set => SetValue(CarNumberProperty, value);
}
public bool HaveRadio { get; set; }
public void GerateRandomCarNumber() { CarNumber = new Random().Next(int.MinValue, int.MaxValue); }
public int CompareTo(int other) { return CarNumber.CompareTo(other); }
public int CompareTo(object obj)
{
Car other = null;
if (obj is Car)
other = obj as Car;
return CarNumber.CompareTo(other.CarNumber);
}
}

(WPF)(MVVM) My ListViewItems are only updated when I change my view

I am creating an application with an MVVM model, in one of my views I have an ObservableCollection where by means of a button I create a new element and it appears on the screen, the problem is that I have a button to update that changes the name of the ListViewItem , and this name doesn't change until I switch between views
Problem
The DNP3-Master are my Items and the button I activate changes the name to "Test" but it is not updated until I change my view (this is a UserControl)
MasterViwModel
class MasterViewModel : ObservableObject
{
public ushort count { get; set; }
public ObservableCollection<MasterTraceModel> MasterReference { get; set; }
public RelayCommand CreateMaster { get; set; }
public RelayCommand Update { get; set; }
private ObservableCollection<MasterModel> _masterList;
public ObservableCollection<MasterModel> MasterList
{
get { return _masterList; }
set { _masterList = value; OnPropertyChanged(); }
}
private MasterModel _selectedMaster;//SelectedItem from ListView
public MasterModel SelectedMaster
{
get { return _selectedMaster; }
set { _selectedMaster = value; OnPropertyChanged(); }
}
public MasterViewModel()
{
MasterList = new ObservableCollection<MasterModel>();//my Observable Collections
//Stuff
this.count = 1;
//Stuff
CreateMaster = new RelayCommand(o =>
{
MasterList.Add(new MasterModel(this.count, "127.0.0.1", "20000", runtime));
this.count = (ushort)(count + 1);
});//Here I add the elements to my ObservableCollections
//Stuff
Update = new RelayCommand(o =>
{
SelectedMaster.SetName("Test");
});
}
}
MasterView
<UserControl x:Class="Prototype.MVVM.View.MasterView"
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:viewmodel="clr-namespace:Prototype.MVVM.ViewModel"
d:DataContext="{d:DesignInstance Type=viewmodel:MasterViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Border Margin="20,20,0,20" Background="#151515" CornerRadius="8">
<ListView Name="MasterListView" Margin="5"
ItemsSource="{Binding MasterList}"
SelectedItem="{Binding SelectedMaster}"
ItemContainerStyle="{StaticResource MasterTheme}"
Background="Transparent"
BorderThickness="0"
/>
</Border>
<StackPanel Grid.Column="1" Margin="0,20,0,0">
<Button Margin="0,0,0,10" Grid.Column="1" Style="{StaticResource SmallBtn}" Command="{Binding Update}">
<Image Height="24" Width="24" Source="/Icons/cil-reload.png" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
MasterModel
class MasterModel : ObservableObject
{
public string Name { get; set; }
public ushort Adress { get; set; }
public string Host { get; set; }
public string Port { get; set; }
public Runtime _runtime { get; set; }
public MasterChannel channel { get; set; }
public ConnectStrategy CStrategy { get; set; }
public string[] Delay { get; set; }
public MasterModel(ushort Adress, string Host, string Port, Runtime runtime)
{
this.Name = "DNP3-Master-" + Adress.ToString();
this.Adress = Adress;
this.Host = Host;
this.Port = Port;
this._runtime = runtime;
CStrategy = new ConnectStrategy();
//CStrategy.MinConnectDelay = new TimeSp
Delay = new string[3];
Delay[0] = CStrategy.MinConnectDelay.ToString();
Delay[1] = CStrategy.MaxConnectDelay.ToString();
Delay[2] = CStrategy.ReconnectDelay.ToString();
this.channel = MasterChannel.CreateTcpChannel(//Stuff);
}
public void SetName(string name)
{
this.Name = name;
}
public void Star(Runtime runtime)
{
Task.Run(async () =>
{
try
{
await MasterFunctions.RunChannel(channel);
}
finally
{
runtime.Shutdown();
}
});
}
The MasterModel class should implement the INotifyPropertyChanged event and raise the PropertyChanged event for the data-bound property when you call SetName:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
Using an ObservableCollection<T> doesn't replace the need to implement INotifyPropertyChanged and raise change notifications for the individual items in the collection. It notifies the view when items are added to and removed from the collection only.

UWP C# How to append .json file and to identify them in groups

I am trying to create a usercontrol to display groups of items from json file on windows iot core.
I have a "Create Group" button. Once pressed, it will create 64 usercontrols with respective details and display in a scrollviewer. susequently i can edit individual items in any of the 64 usercontrol then save the json file.
I have my class for the usercontrol as follow;
I have having an issue on how to create different groups of the 64 items and append all in a same json file and to subsequently display them from selection of the mentioned different groups.
Please help thanks.
Group Class
[DataContract]
public class DecoderGroup
{
[DataMember]
public int groupID{ get; set; }
[DataMember]
public string groupName{ get; set; }
[DataMember]
public int cardAddress { get; set; }
[DataMember]
public bool enabled { get; set; }
[DataMember]
public int z1label { get; set; }
[DataMember]
public int z2label { get; set; }
[DataMember]
public int z3label { get; set; }
[DataMember]
public int z4label { get; set; }
[DataMember]
public bool zone1 { get; set; }
[DataMember]
public bool zone2 { get; set; }
[DataMember]
public bool zone3 { get; set; }
[DataMember]
public bool zone4 { get; set; }
[DataMember]
public List<byte> txData { get; set; }
public DecoderGroup(int id, int address, int z1, int z2, int z3, int z4)
{
groupName = "Group";
zone1 = false;
zone2 = false;
zone3 = false;
zone4 = false;
z1label = z1;
z2label = z2;
z3label = z3;
z4label = z4;
}
}
MainPage.xaml.cs
private void AddGroup_Click(object sender, RoutedEventArgs e)
{
ZonesList_Panel.Children.Clear();
int groupid = 1;
int cardadr;
for (cardadr = 1; cardadr <= MAXCARDS; cardadr++)
{
var z4 = (4 * cardadr);
var z3 = (4 * cardadr) - 1;
var z2 = (4 * cardadr) - 2;
var z1 = (4 * cardadr) - 3;
DecoderGroupUserControl decoderGroupControl = new DecoderGroupUserControl(this, new DecoderGroup(groupid, cardadr, z1, z2, z3, z4));
ZonesList_Panel.Children.Add(decoderGroupControl);
}
}
private async void SaveGroup_Click(object sender, RoutedEventArgs e)
{
await saveGroupsToJSON(getGroups());
}
public async Task saveGroupsToJSON(List<DecoderGroup> groups)
{
var serializer = new DataContractJsonSerializer(typeof(List<DecoderGroup>));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(DECODERGROUPS_FILE, CreationCollisionOption.OpenIfExists))
{
serializer.WriteObject(stream, groups);
}
}
public List<DecoderGroup> getGroups()
{
List<DecoderGroup> ret = new List<DecoderGroup>();
foreach (DecoderGroupUserControl u in ZonesList_Panel.Children)
{
//add condition for group ID
ret.Add(u.decoderGroup);
}
return ret;
}
UserControl
public DecoderGroupUserControl(MainPage page, DecoderGroup group)
{
this.InitializeComponent();
mainPage = page;
this.decoderGroup = group;
Z1Name.Text = group.z1label.ToString();
Z2Name.Text = group.z2label.ToString();
Z3Name.Text = group.z3label.ToString();
Z4Name.Text = group.z4label.ToString();
}
It is recommended to use UserControl as the DateTemplate of ListView so that you don’t need to create multiple usercontrol and add them to the page. Then you could read json file and convert json objects to a collection, and you could use the collection as the Itemsource of ListView.
By implementing the INotifyPropertyChanged interface, TwoWay data binding could reflect the UI changes to the collection. Finally, you could write the changed collection to the json file.
Note that you need to download the Newtonsoft.Json to parse the json object via Manage NuGet Packages. Please refer to the following code.
MyUserControl1.xaml:
<UserControl
..>
<Grid>
<!--customize the usercontrol style-->
<StackPanel>
<StackPanel Orientation="Horizontal" >
<TextBox Margin="0,0,20,0" Text="{Binding Name,Mode=TwoWay}" BorderThickness="0"/>
<TextBox Text="{Binding Job,Mode=TwoWay}" BorderThickness="0"/>
</StackPanel>
<TextBox Text="{Binding Age,Mode=TwoWay}" BorderThickness="0"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml:
<Page..>
<Grid>
<StackPanel>
<ListView ItemsSource="{x:Bind Results,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Person">
<local:MyUserControl1>
</local:MyUserControl1>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="SaveButton" Content="Save" Click="SaveButton_Click"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs:
namespace WriteJson
{
public sealed partial class MainPage : Page
{
public ObservableCollection<Person> Persons { get; set; }
public ObservableCollection<Person> Results { get; set; }
public string path;
public MainPage()
{
this.InitializeComponent();
CreateJsonFile();
path = ApplicationData.Current.LocalFolder.Path + "\\info.json";
Results = JsonConvert.DeserializeObject<ObservableCollection<Person>>(File.ReadAllText(path));
Debug.WriteLine("bind successfully");
}
public async void CreateJsonFile()
{
//check if info.json exists, if it doesn't exist, create it
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file;
try
{
file = await folder.GetFileAsync("info.json");
}
catch
{
await folder.CreateFileAsync("info.json");
Persons = new ObservableCollection<Person>()
{
new Person(){Name="tom",Job="teacher",Age=24},
new Person(){Name="lily",Job="nurse",Age=20},
new Person(){Name="ming",Job="student",Age=26},
new Person(){Name="kiki",Job="lawyer",Age=28},
new Person(){Name="jack",Job="worker",Age=21},
};
path = ApplicationData.Current.LocalFolder.Path + "\\info.json";
File.WriteAllText(path, JsonConvert.SerializeObject(Persons));
Debug.WriteLine("create a json file successfully");
}
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
File.WriteAllText(path, JsonConvert.SerializeObject(Results));
Debug.WriteLine("save successfully");
}
}
public class Person:INotifyPropertyChanged
{
private string name;
private string job;
private int age;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public string Job
{
get { return job; }
set
{
job = value;
RaisePropertyChanged("Job");
}
}
public int Age
{
get { return age; }
set
{
age = value;
RaisePropertyChanged("Age");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyname=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
}

Binding a TreeView to a collection with different types of objects on same level

I want to bind my Treeview control to a collection that holds objects of type MeasurementResult. The MeasurementResult object itself has two collections, one for MeasurementInfo and one for DeviceInfo types.
After googling and searching on SO I found out that the best solution might be a CompositeCollection. The problem I have with that is that I just can't figure out how to define the (Hierarchical?!)DataTemplate's in a way that my data get shown in the Treeview in the way I want it.
Ultimately I would like to have a TreeView structure like that:
-MeasurementResult1
---MeasurementInformation
------MeasurementInformation1
------MeasurementInformation2
------MeasurementInformation3
---DeviceInformation
------DeviceInformation1
------DeviceInformation2
------DeviceInformation3
-MeasurementResult2
---MeasurementInformation
------MeasurementInformation1
------MeasurementInformation2
------MeasurementInformation3
---DeviceInformation
------DeviceInformation1
------DeviceInformation2
------DeviceInformation3
-MeasurementResultN
But the problem is that my current Treeview looks like that:
The nested properties for MeasurementData and DeviceData are not shown in my TreeView.
The code that I have so far, XAML:
<local:TreeViewSampleData x:Key="TreeViewSampleData"/>
<DataTemplate x:Key="MeasurementDataTemplate">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finished: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Finished}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Median: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Median}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Maximum: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Maximum}" />
</StackPanel>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="DeviceDataTemplate" DataType="{x:Type local:DeviceData}" ItemTemplate="{StaticResource MeasurementDataTemplate}"
ItemsSource="{Binding MeasurementData}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="MeasurementResultTemplate" DataType="{x:Type local:MeasurementResult}" ItemTemplate="{StaticResource DeviceDataTemplate}"
ItemsSource="{Binding Measurements}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<telerik:RadTreeView x:Name="tvMeasResults"
ItemsSource="{Binding Source={StaticResource TreeViewSampleData}, Path = MeasurementResults}"
ItemTemplate="{StaticResource MeasurementResultTemplate}"
>
</telerik:RadTreeView>
My related classes:
public class MeasurementResult
{
public string Name { get; set; } = "Measurement Result";
internal ObservableCollection<MeasurementInfo> MeasurementInfo { get; set; }
internal ObservableCollection<DeviceInfo> DeviceInfo { get; set; }
public CompositeCollection Measurements
{
get
{
var items = new CompositeCollection();
items.Add(new CollectionContainer { Collection = MeasurementInfo });
items.Add(new CollectionContainer { Collection = DeviceInfo });
return items;
}
}
public MeasurementResult()
{
MeasurementInfo = new ObservableCollection<MeasurementInfo>();
DeviceInfo = new ObservableCollection<DeviceInfo>();
}
}
public class MeasurementInfo
{
public string Name { get; set; } = "Measurement Information";
public ObservableCollection<MeasurementData> ThicknessData { get; set; }
public MeasurementInfo()
{
ThicknessData = new ObservableCollection<MeasurementData>();
}
}
public class MeasurementData
{
public DateTime Finished { internal set; get; }
public double Median { internal set; get; }
public double Maximum { internal set; get; }
public MeasurementData()
{
Finished = DateTime.Now;
Median = 150;
Maximum = 200;
}
}
public class DeviceInfo
{
public string Name { get; set; } = "Device Information";
public ObservableCollection<DeviceData> DeviceData { get; set; }
public DeviceInfo()
{
DeviceData = new ObservableCollection<DeviceData>();
}
}
public class DeviceData
{
public DateTime Finished { internal set; get; }
public int Port { internal set; get; }
public int Slot { internal set; get; }
public DeviceData()
{
Finished = DateTime.Now;
Port = 1;
Slot = 1;
}
}
What is wrong with my bindings? I guess the DataTemplates are wrong but I couldn't figure out how to define them to get my expected result.
This will allow you to add specific items to specific leaves and they will be concatenated by GetEnumerator so the TreeView presents things in the way you expected.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var item = new InformationTreeItem("ROOT")
{
Children =
{
new InformationTreeItem("Level 1")
{
DeviceInformation =
{
new DeviceInformation("Device 1/1"),
new DeviceInformation("Device 1/2")
},
MeasurementInformation =
{
new MeasurementInformation("Measure 1/1"),
new MeasurementInformation("Measure 1/2")
},
Children =
{
new InformationTreeItem("Level 2")
{
DeviceInformation =
{
new DeviceInformation("Device 2/1"),
new DeviceInformation("Device 2/2")
},
MeasurementInformation =
{
new MeasurementInformation("Measure 2/1"),
new MeasurementInformation("Measure 2/2")
},
Children =
{
new InformationTreeItem("Level 3")
}
}
}
}
}
};
DataContext = item;
}
}
public interface IInformation
{
string Description { get; }
}
public class InformationTreeItem : IEnumerable<IInformation>, IInformation
{
public InformationTreeItem(string description)
{
Description = description;
}
private InformationTreeItem(string description, IList<IInformation> children)
{
Description = description;
Children = children;
}
public IList<IInformation> Children { get; } = new List<IInformation>();
public IList<DeviceInformation> DeviceInformation { get; } = new List<DeviceInformation>();
public IList<MeasurementInformation> MeasurementInformation { get; } = new List<MeasurementInformation>();
public string Description { get; }
public IEnumerator<IInformation> GetEnumerator()
{
var list = new List<IInformation>();
if (DeviceInformation.Any())
{
list.Add(new InformationTreeItem(nameof(DeviceInformation), new List<IInformation>(DeviceInformation)));
}
if (MeasurementInformation.Any())
{
list.Add(new InformationTreeItem(nameof(MeasurementInformation), new List<IInformation>(MeasurementInformation)));
}
foreach (var child in Children)
{
list.Add(child);
}
return list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
return Description;
}
}
public class DeviceInformation : IInformation
{
public DeviceInformation(string description)
{
Description = description;
}
public string Description { get; }
public override string ToString()
{
return Description;
}
}
public class MeasurementInformation : IInformation
{
public MeasurementInformation(string description)
{
Description = description;
}
public string Description { get; }
public override string ToString()
{
return Description;
}
}
}

Changes in SelectedItem of ComboBox alter ItemsSource

I have a ComboBox and a DataGrid in my XAML:
<ComboBox SelectedItem="{Binding SelectedFamily}" ItemsSource="{Binding FamilyList}" IsSynchronizedWithCurrentItem="True"/>
<DataGrid ItemsSource="{Binding FamilyInfoGrid}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Age" Binding="{Binding Age}" />
</DataGrid.Columns>
</DataGrid>
This is my viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ReadOnlyObservableCollection<Family> _familyList;
public ReadOnlyObservableCollection<Family> FamilyList
{
get { return _familyList; }
}
private Family _selectedFamily;
public Family SelectedFamily
{
get
{
return _selectedFamily;
}
set
{
_familyInfoGrid.Clear();
_familyInfoGrid.Add(value.Kid);
_familyInfoGrid.Add(value.Parent2);
_familyInfoGrid.Add(value.Parent1);
RaisePropertyChangedEvent();
}
}
private ObservableCollection<Person> _familyInfoGrid = new ObservableCollection<Person>();
public ObservableCollection<Person> FamilyInfoGrid
{
get { return _familyInfoGrid; }
set { _familyInfoGrid = value; RaisePropertyChangedEvent(); }
}
public MainWindowViewModel()
{
var fam1 = new Family("Smith", new Person("Jim", 31), new Person("Eve", 29), new Person("Tom", 2));
var fam2 = new Family("Miller", new Person("Joe", 35), new Person("Sue", 33), new Person("Kim", 8));
_familyList = new ReadOnlyObservableCollection<Family>(new ObservableCollection<Family>() { fam1, fam2 } );
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
which means the DataGrid always gets updated to the currently selected item of the combobox - as it should.
This my model:
public class Family
{
public string Name { get; set; }
public Person Kid { get; set; }
public Person Parent1 { get; set; }
public Person Parent2 { get; set; }
public Family(string name, Person parent1, Person parent2, Person kid)
{
Name = name; Parent1 = parent1; Parent2 = parent2; Kid = kid;
}
public override string ToString()
{
return Name;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name; Age = age;
}
}
The problem is that changes that are made within the DataGrid are stored in the _familyList, but I want the _familyList to be immutable and only be able to edit (and use) the current fields of the DataGrid temporarily (so Mode=OneWay is not an option either).
Thanks to #mm8 I managed to find a solution. The currently selected item of the combobox has to be a "deep copied":
public Family SelectedFamily
{
set
{
var fam = new Family(
new string(value.Name.ToCharArray()),
new Person(value.Parent1.Name, value.Parent1.Age),
new Person(value.Parent2.Name, value.Parent2.Age),
new Person(value.Kid.Name, value.Kid.Age));
_familyInfoGrid.Clear();
_familyInfoGrid.Add(fam.Kid);
_familyInfoGrid.Add(fam.Parent2);
_familyInfoGrid.Add(fam.Parent1);
RaisePropertyChangedEvent();
}
}

Categories