I have a DependencyObject class composition that looks like the following:
public class A : DependencyObject {
public AB AB { get { ... } set { ... } }
public AB AC { get { ... } set { ... } }
}
public class AB : DependencyObject {
public string Property1 { get { ... } set { ... } }
public string Property2 { get { ... } set { ... } }
public string Property3 { get { ... } set { ... } }
}
public class AC : DependencyObject {
public string Property1 { get { ... } set { ... } }
public string Property2 { get { ... } set { ... } }
}
On A, AB and AC all properties perform the typical GetValue and SetValue operations referencing static properties per usual.
Now, classes A, AB and AC have corresponding UserControls AGroupBox, ABGrid, ACGrid. AGroupBox has a root A class property, ABGrid has a root AB class property and ACGrid has a root AC class property.
Both ABGrid and ACGrid have working bindings (e.g., ABGrid Contains a TextBox control whose Text property is twoway bound to AB's Property1.) I've verified this by creating a simple Window and having ABGrid be Window's only Content child and in the code behind setting ABGrid.AB = new AB(); same scenario for ACGrid.AC = new AC();.
The problem is when I try to do similarlly with with AGroupBox. I try adding AGroupBox as the single child of Window's Content in XAML, and set the AGroupBox.A property to new A() {AB = new AB(), AC = new AC()}; and the binding of the controls fails. AB and AC have default values for their PropertyN properties.
Any insights on what I'm missing? Is there a different route I should be taking?
EDIT: Additional Comment- If I add a string property to A, (String1) and bind it to the Text part of the GroupBox then the binding to that property works, but not to the AC and AB property of A.
EDIT-2: Per David Hay's request (all code is in namespace wpfStackOverflow):
A.cs
public class A : DependencyObject {
static public DependencyProperty BProperty { get; private set; }
static public DependencyProperty CProperty { get; private set; }
static public DependencyProperty PropertyProperty { get; private set; }
static A() {
BProperty = DependencyProperty.Register("B", typeof(B), typeof(A));
CProperty = DependencyProperty.Register("C", typeof(C), typeof(A));
PropertyProperty = DependencyProperty.Register("Property", typeof(string), typeof(A));
}
public B B {
get { return (B)GetValue(BProperty); }
set { SetValue(BProperty, value); }
}
public C C {
get { return (C)GetValue(CProperty); }
set { SetValue(CProperty, value); }
}
public string Property {
get { return (string)GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public A() {
Property = "A's Default Value";
B = new B();
C = new C();
}
}
B.cs
public class B : DependencyObject {
static public DependencyProperty PropertyProperty { get; private set; }
static B() {
PropertyProperty = DependencyProperty.Register("Property", typeof(string), typeof(B));
}
public string Property {
get { return (string)GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public B() {
Property = "B's Default Value";
}
}
C.cs
public class C : DependencyObject {
static public DependencyProperty PropertyProperty { get; private set; }
static C() {
PropertyProperty = DependencyProperty.Register("Property", typeof(string), typeof(C));
}
public string Property {
get { return (string)GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public C() {
Property = "C's Default Value";
}
}
AGroupBox.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpfStackOverflow"
x:Class="wpfStackOverflow.AGroupBox"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=A}"
Width="300"
Height="72"
>
<GroupBox Header="{Binding Property}">
<StackPanel >
<local:BGrid B="{Binding B}"/>
<local:CGrid C="{Binding C}"/>
</StackPanel>
</GroupBox>
</UserControl>
AGroupBox.xaml.cs
public partial class AGroupBox : UserControl {
static public DependencyProperty AProperty { get; private set; }
static AGroupBox() {
AProperty = DependencyProperty.Register("A", typeof(A), typeof(AGroupBox));
}
public A A {
get { return (A)GetValue(AProperty); }
set { SetValue(AProperty, value); }
}
public AGroupBox() {
InitializeComponent();
}
}
BGrid.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="wpfStackOverflow.BGrid"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=B}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Property"/>
<TextBox Grid.Column="1" Text="{Binding Property}"/>
</Grid>
</UserControl>
BGrid.xaml.cs
public partial class BGrid : UserControl {
static public DependencyProperty BProperty { get; private set; }
static BGrid() {
BProperty = DependencyProperty.Register("B", typeof(B), typeof(BGrid));
}
public B B {
get { return (B)GetValue(BProperty); }
set { SetValue(BProperty, value); }
}
public BGrid() {
InitializeComponent();
}
}
CGrid.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="wpfStackOverflow.CGrid"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=C}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Property"/>
<TextBox Grid.Column="1" Text="{Binding Property}"/>
</Grid>
</UserControl>
CGrid.xaml.cs
public partial class CGrid : UserControl {
static public DependencyProperty CProperty { get; private set; }
static CGrid() {
CProperty = DependencyProperty.Register("C", typeof(C), typeof(CGrid));
}
public C C {
get { return (C)GetValue(CProperty); }
set { SetValue(CProperty, value); }
}
public CGrid() {
InitializeComponent();
}
}
window1.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpfStackOverflow"
x:Class="wpfStackOverflow.Window1"
Width="400"
Height="200"
>
<local:AGroupBox x:Name="aGroupBox" />
</Window>
Window1.xaml.cs
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
aGroupBox.A = new A()
{
Property = "A's Custom Property Value",
B = new B()
{
Property = "B's Custom Property Value"
},
C = new C()
{
Property = "C's Custom Property Value"
}
};
}
}
Try substituting the following into AGroupBox.xaml
<local:BGrid B="{Binding Path=A.B, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:AGroupBox}}}"/>
<local:CGrid C="{Binding Path=A.C, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:AGroupBox}}}"/>
It was not resolving the datacontext properly for those two lines, and so was not looking in the right place for B and C.
Related
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);
}
}
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.
I have an ItemTemplateSelector which contains Multiple DataTemplates which have Different DataTypes.
I thus have multiple ItemSources based on Module Selected.
How to bind my ListView with multiple ItemSources based on the module selected?
Explanation:
1)ViewModel_A is my ItemSource and DataTemplateA is my DataTemplate when my Module A is Selected
2)ViewModel_B is my ItemSource DataTemplateB is my DataTemplate when my Module B is Selected
I tried Implementing a BaseViewModel and tried binding the BaseViewModel Type in my ItemSource But this doesn't allow the access of derived class properties.
How to Dynamically Select My ItemSource?
Step 1
First Create a UserControl which contains your ListView in your Xaml and two DependancyProperty for ItemSource and DataTemplate
DataList.Xaml
<UserControl
x:Class="MultipleDataTemplate.DataList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<ListView ItemsSource="{x:Bind ItemsSource,Mode=OneWay}"></ListView>
</Grid>
</UserControl>
DataList.xaml.cs
public sealed partial class DataList : UserControl
{
public DataList()
{
this.InitializeComponent();
}
#region ItemsSource
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(DataList), new PropertyMetadata(null));
#endregion
#region ItemTemplate
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(DataList), new PropertyMetadata(null));
#endregion
}
Step 2
Now you can you this usercontrol with any multiple DataTemplate's and multiple itemsource as below
MainPage.xaml
<Page
x:Class="MultipleDataTemplate.Cars"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:MultipleDataTemplate">
<Page.Resources>
<DataTemplate x:Key="CarKey" x:DataType="controls:Car">
<Grid>
<TextBlock Text="{x:Bind carprop1}"></TextBlock>
<TextBlock Text="{x:Bind carprop2}"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="BikeKey" x:DataType="controls:Bike">
<Grid>
<TextBlock Text="{x:Bind Bikeprop1}"></TextBlock>
<TextBlock Text="{x:Bind Bikeprop2}"></TextBlock>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<controls:DataList ItemsSource="{x:Bind ItemSource,Mode=OneWay}" ItemTemplate="{x:Bind ItemTemplate}"></controls:DataList>
<StackPanel>
<Button Content="Cars" Click="CarsClick"/>
<Button Content="Bike" Click="BikeClick"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class Cars : Page, INotifyPropertyChanged
{
public object _ItemSource { get; set; }
public object ItemSource
{
get { return _ItemSource; }
set
{
_ItemSource = value;
this.OnPropertyChanged();
}
}
public DataTemplate _itemTemplate { get; set; }
public DataTemplate ItemTemplate
{
get { return _itemTemplate; }
set
{
_itemTemplate = value;
this.OnPropertyChanged();
}
}
public Cars()
{
this.InitializeComponent();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private void CarsClick(object sender, RoutedEventArgs e)
{
ItemSource = new List<Car>() { new Car() { carprop1 = "1", carprop2 = "2" } };
ItemTemplate = this.Resources["CarKey"] as DataTemplate;
}
private void BikeClick(object sender, RoutedEventArgs e)
{
ItemSource = new List<Bike>() { new Bike() { Bikeprop1 = "1", Bikeprop2 = "2" } };
ItemTemplate = this.Resources["BikeKey"] as DataTemplate;
}
}
public class Car
{
public string carprop1 { get; set; }
public string carprop2 { get; set; }
}
public class Bike
{
public string Bikeprop1 { get; set; }
public string Bikeprop2 { get; set; }
}
Maybe this is quiet simple question but I have a problems with construction of a template for my treeview. I have some classes:
public class A //main class
{
public B sth { get; set; }
public C sthelse { get; set; }
public A()
{
this.sth = new B(1000, "sth");
this.sthelse = new C();
}
}
public class B
{
public D sth { get; set; }
public B(ulong data, String abc)
{
this.sth = new D(data, abc);
}
}
public class D
{
public ulong data { get; private set; }
public String abc { get; private set; }
public D(ulong data, String abc)
{
this.data = data;
this.abc = abc;
}
}
And my question is how can I put it into treeview. I was testing HierarchicalDataTemplate but problem is that it have to be bound to collection. Any ideas how to create treeview like this:
A
B
D
data
abc
C
Is it possible?
I am using this code:
<TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource phy}" />
<Window.Resources>
<DataTemplate x:Key="d">
<StackPanel Orientation="Vertical">
<!-- Maybe there should be pairs property - value, maybe grid or whatever -->
<TextBlock Text="{Binding Path=data}" />
<TextBlock Text="{Binding Path=abc}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="b" ItemsSource="{Binding Path=sth}" ItemTemplate="{StaticResource ResourceKey=d}">
<TextBlock Text="D" />
</HierarchicalDataTemplate>
<!-- Cant bind also attribute C -->
<HierarchicalDataTemplate x:Key="phy" ItemsSource="{Binding Path=sth}" ItemTemplate="{StaticResource ResourceKey=b}">
<TextBlock Text="PHY" />
</HierarchicalDataTemplate>
</Window.Resources>
In code is:
public ObservableCollection<A> data { get; private set; }
And in constructor:
data = new ObservableCollection<A>();
treeView1.DataContext = data;
data.Add(new A());
ItemsSource property values must be IEnumerable. There's no way to avoid this. You can expose IEnumerables in a very simple way such as below, but I would recommend a better object model than this. You can take these classes and bind the ItemsSource properties of the tree and the HierarchicalDataTemplate to this new Nodes property.
public class A //main class
{
public B sth { get; set; }
public C sthelse { get; set; }
public A()
{
this.sth = new B(1000, "sth");
this.sthelse = new C();
}
public IEnumerable<object> Nodes
{
get
{
yield return B;
yield return C;
}
}
}
public class B
{
public D sth { get; set; }
public B(ulong data, String abc)
{
this.sth = new D(data, abc);
}
public IEnumerable<object> Nodes
{
get
{
yield return D;
}
}
}
public class D
{
public ulong data { get; private set; }
public String abc { get; private set; }
public D(ulong data, String abc)
{
this.data = data;
this.abc = abc;
}
public IEnumerable<object> Nodes
{
get
{
yield return data;
yield return abc;
}
}
}
I am currently implementing the application that displays hierarchy using ListBoxes (please do not suggest using TreeView, ListBoxes are needed).
It looks like that in the article: WPF’s CollectionViewSource (with source code).
Classes:
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
}
public class Lift
{
public ObservableCollection<string> Runs { get; }
}
The example uses CollectionViewSource instances (see XAML) to simplify the design.
An instance of Mountains class is the DataContext for the window.
The problem is: I would like that the Mountains class to have SelectedRun property and it should be set to currently selected run.
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
public string SelectedRun { get; set; }
}
Maybe I've missed something basic principle, but how can I achieve this?
You may want to read about the use of '/' in bindings. See the section 'current item pointers' on this MSDN article.
Here's my solution:
Xaml
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="0" Text="Mountains"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="1" Text="Lifts"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="2" Text="Runs"/>
<ListBox Grid.Row="1" Grid.Column="0" Margin="5"
ItemsSource="{Binding Mountains}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ListBox Grid.Row="1" Grid.Column="1" Margin="5"
ItemsSource="{Binding Mountains/Lifts}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"/>
<ListBox Grid.Row="1" Grid.Column="2" Margin="5"
ItemsSource="{Binding Mountains/Lifts/Runs}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedRun}"/>
</Grid>
C# (note, you don't need to implement INotifyPropertyChanged unless the properties will be changed and not just selected)
public class MountainsViewModel
{
public MountainsViewModel()
{
Mountains = new ObservableCollection<Mountain>
{
new Mountain
{
Name = "Whistler",
Lifts = new ObservableCollection<Lift>
{
new Lift
{
Name = "Big Red",
Runs = new ObservableCollection<string>
{
"Headwall",
"Fisheye",
"Jimmy's"
}
},
new Lift
{
Name = "Garbanzo",
Runs = new ObservableCollection<string>
{
"Headwall1",
"Fisheye1",
"Jimmy's1"
}
},
new Lift {Name = "Orange"},
}
},
new Mountain
{
Name = "Stevens",
Lifts = new ObservableCollection<Lift>
{
new Lift {Name = "One"},
new Lift {Name = "Two"},
new Lift {Name = "Three"},
}
},
new Mountain {Name = "Crystal"},
};
}
public string Name { get; set; }
private string _selectedRun;
public string SelectedRun
{
get { return _selectedRun; }
set
{
Debug.WriteLine(value);
_selectedRun = value;
}
}
public ObservableCollection<Mountain> Mountains { get; set; }
}
public class Mountain
{
public string Name { get; set; }
public ObservableCollection<Lift> Lifts { get; set; }
}
public class Lift
{
public string Name { get; set; }
public ObservableCollection<string> Runs { get; set; }
}
Here's how I would do it. You want to make sure that you fire the INotifyPropertyChanged event when setting the properties. To get the Selected Run you'll have to get MainViewModel.SelectedMountain.SelectedLift.SelectedRun.
public class MainViewModel: ViewModelBae
{
ObservableCollection<MountainViewModel> mountains
public ObservableCollection<MountainViewModel> Mountains
{
get { return mountains; }
set
{
if (mountains != value)
{
mountains = value;
RaisePropertyChanged("Mountains");
}
}
}
MountainViewModel selectedMountain
public MountainViewModel SelectedMountain
{
get { return selectedMountain; }
set
{
if (selectedMountain != value)
{
selectedMountain = value;
RaisePropertyChanged("SelectedMountain");
}
}
}
}
public class MountainViewModel: ViewModelBae
{
ObservableCollection<LiftViewModel> lifts
public ObservableCollection<LiftViewModel> Lifts
{
get { return lifts; }
set
{
if (lifts != value)
{
lifts = value;
RaisePropertyChanged("Lifts");
}
}
}
LiftViewModel selectedLift
public LiftViewModel SelectedLift
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
public class LiftViewModel: ViewModelBae
{
ObservableCollection<string> runs
public ObservableCollection<string> Runs
{
get { return runs; }
set
{
if (runs != value)
{
runs = value;
RaisePropertyChanged("Runs");
}
}
}
string selectedRun
public string SelectedRun
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
<ListBox ItemsSource="{Binding Mountains}" SelectedItem="{Binding SelectedMountain, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.Lifts}" SelectedItem="{Binding SelectedMountain.SelectedLift, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.SelectedLift.Runs}" SelectedItem="{Binding SelectedMountain.SelectedLift.SelectedRun, Mode=TwoWay}">
Your ViewModel should not also be a collection, it should contain collections and properties which are bound to the view. SelectedRun should be a property of this ViewModel (MountainViewModel) not Mountains. MountainViewModel should expose the Mountains collection and SelectedRun and should be bound to the listboxes' ItemsSource and SelectedItem.