Databinding to a heterogeneous list - c#

I have a WPF UserControl with a ListBox and ContentPanel. The ListBox is bound to a ObservableCollection that has apples and oranges in it.
What is considered the proper way to have it setup so if I select an apple I see an AppleEditor on the right and if I select an orange an OrangeEditor shows up in the content panel?

I would suggest using DataTemplating to create and apply the different editors. Depending on how different your 'apples' and 'oranges' are I would recommend using a DataTemplateSelector. Also, if they had something like a Type property you could also use DataTriggers to switch out the editors.
Lets set up a small sample with apples and oranges. They'll have some shared properties, and a few different properties as well. And then we can create an ObservableCollection of the base IFruits to use in the UI.
public partial class Window1 : Window
{
public ObservableCollection<IFruit> Fruits { get; set; }
public Window1()
{
InitializeComponent();
Fruits = new ObservableCollection<IFruit>();
Fruits.Add(new Apple { AppleType = "Granny Smith", HasWorms = false });
Fruits.Add(new Orange { OrangeType = "Florida Orange", VitaminCContent = 75 });
Fruits.Add(new Apple { AppleType = "Red Delicious", HasWorms = true });
Fruits.Add(new Orange { OrangeType = "Navel Orange", VitaminCContent = 130 });
this.DataContext = this;
}
}
public interface IFruit
{
string Name { get; }
string Color { get; }
}
public class Apple : IFruit
{
public Apple() { }
public string AppleType { get; set; }
public bool HasWorms { get; set; }
#region IFruit Members
public string Name { get { return "Apple"; } }
public string Color { get { return "Red"; } }
#endregion
}
public class Orange : IFruit
{
public Orange() { }
public string OrangeType { get; set; }
public int VitaminCContent { get; set; }
#region IFruit Members
public string Name { get { return "Orange"; } }
public string Color { get { return "Orange"; } }
#endregion
}
Next, we can create DataTemplateSelector, that will just check the type of the Fruit and assign the correct DataTemplate.
public class FruitTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
string templateKey = null;
if (item is Orange)
{
templateKey = "OrangeTemplate";
}
else if (item is Apple)
{
templateKey = "AppleTemplate";
}
if (templateKey != null)
{
return (DataTemplate)((FrameworkElement)container).FindResource(templateKey);
}
else
{
return base.SelectTemplate(item, container);
}
}
}
Then in the UI, we can create the two templates for Apples and Oranges, and use the selector to determine which gets applied to our content.
<Window x:Class="FruitSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FruitSample"
Title="Fruits"
Height="300"
Width="300">
<Window.Resources>
<local:FruitTemplateSelector x:Key="Local_FruitTemplateSelector" />
<DataTemplate x:Key="AppleTemplate">
<StackPanel Background="{Binding Color}">
<TextBlock Text="{Binding AppleType}" />
<TextBlock Text="{Binding HasWorms, StringFormat=Has Worms: {0}}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OrangeTemplate">
<StackPanel Background="{Binding Color}">
<TextBlock Text="{Binding OrangeType}" />
<TextBlock Text="{Binding VitaminCContent, StringFormat=Has {0} % of daily Vitamin C}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<ListBox x:Name="uiFruitList"
ItemsSource="{Binding Fruits}"
DisplayMemberPath="Name" />
<ContentControl Content="{Binding Path=SelectedItem, ElementName=uiFruitList}"
ContentTemplateSelector="{StaticResource Local_FruitTemplateSelector}"/>
</DockPanel>
</Window>

Related

Binding different types of selected item in hierarchical treeview?

I have a hierarchical list of objects whose children are objects of a different type. The classes for them are described as follows:
public class Production
{
public string name { get; set; }
public string id { get; set; }
public List<Plant> plants { get; set; }
}
public class Plant
{
public string name { get; set; }
public string id { get; set; }
public List<Scheme> schemes { get; set; }
}
public class Scheme
{
public string name { get; set; }
public string id { get; set; }
}
And the main class which contains a list of productions and methods for filling the main menu:
public class DocumentProviderMenu
{
public List<Production> productions { get; set; }
public DocumentProviderMenu()
{
ExecuteUpdateMenu();
}
private void ExecuteUpdateMenu() {/*Uploding menu method}
Finally, the TreeView xaml:
<TreeView ItemsSource="{Binding Menu.productions}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type mod:Production}"
ItemsSource="{Binding plants}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type mod:Plant}"
ItemsSource="{Binding schemes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type mod:Scheme}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Where Menu is a property of ViewModel.So I declared the fields in ViewModel like:
public Production SelectedProduction
{
get => _SelectedProduction;
set
{
_SelectedProduction = value;
OnPropertyChanged(nameof(SelectedProduction));
}
}
private Production _SelectedProduction;
for 3 types - Production,Plant,Scheme.I can bind the selected item but only to one type (this question helped me Data binding to SelectedItem in a WPF Treeview). Is there a way to bind 3 of my types to the selected item?
These elements must have something in common, e.g. an interface:
XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1" Width="640" Height="480"
mc:Ignorable="d" d:DataContext="{d:DesignInstance local:Model}">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding SelectedProduction, StringFormat='{}Selected item: {0}' }" />
<TreeView ItemsSource="{Binding Productions}" SelectedItemChanged="TreeView_OnSelectedItemChanged" Grid.Row="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Production}"
ItemsSource="{Binding Plants}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Plant}"
ItemsSource="{Binding Schemes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Scheme}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1;
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
(DataContext as Model)!.SelectedProduction = e.NewValue as ITreeElement;
}
}
internal class Model : INotifyPropertyChanged
{
private ITreeElement? _selectedProduction;
public Model()
{
Productions = new List<ITreeElement>
{
new Production
{
Name = "production",
Plants = new List<Plant>
{
new()
{
Name = "plant 1",
Schemes = new List<Scheme>
{
new()
{
Name = "scheme 1"
},
new()
{
Name = "scheme 2"
}
}
},
new()
{
Name = "plant 2",
Schemes = new List<Scheme>
{
new()
{
Name = "scheme 3"
},
new()
{
Name = "scheme 4"
}
}
}
}
}
};
}
public List<ITreeElement> Productions { get; }
public ITreeElement? SelectedProduction
{
get => _selectedProduction;
set => SetField(ref _selectedProduction, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public interface ITreeElement
{
string Name { get; set; }
string Id { get; set; }
}
public class Production : ITreeElement
{
public List<Plant> Plants { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}
public class Plant : ITreeElement
{
public List<Scheme> Schemes { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}
public class Scheme : ITreeElement
{
public string Name { get; set; }
public string Id { get; set; }
}

TabControl the variable changes in all TabItems

I have a list(TabItemViewModel) which are binding to TabControl to generate TabItems and inside TabItemViewModel class I have second list(LanguageTexts) with some strings. But when I change variable value in anyone element in class LanguageTexts hViewModel.Items[0].languageTexts[0].ownedVersion = "test"; this are changing in all tabs, but I want only to change in one particular tab.
XAML:
<TabControl ItemsSource="{Binding Path=Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=languageTexts[0].ownedVersion}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" FontSize="18"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C#
public class TabControlViewModel
{
public ObservableCollection<TabItemViewModel> Items { get; set; } = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel {Name="Tab 1", IsSelected = true },
new TabItemViewModel {Name="Tab 2" },
new TabItemViewModel {Name="Tab 3" },
new TabItemViewModel {Name="Tab 4" },
};
}
public class TabItemViewModel
{
public ObservableCollection<LanguageTexts> languageTexts { get; private set; } = new ObservableCollection<LanguageTexts>();
public string Name { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
DoSomethingWhenSelected();
}
}
private void DoSomethingWhenSelected()
{
if (isSelected)
Debug.WriteLine("You selected " + Name);
}
}
public class LanguageTexts : INotifyPropertyChanged
{
private string _ownedVersion;
public string ownedVersion
{
get
{
return _ownedVersion;
}
set
{
if (value != _ownedVersion)
{
_ownedGameVersionTXT = value;
OnPropertyChanged();
}
}
}
public MainWindow()
{
InitializeComponent();
LanguageTexts languageTexts = new LanguageTexts("en_US");
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(languageTexts);
}
Since LanguageTexts is a class, each reference in your view models reference the same texts (class is a reference type). For each view to have its own copy, you would need to make a new copy for each view.
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(new LanguageTexts("en_US"));
}

WPF, Caliburn.Micro and Dapper ComboBoxes

I am just new to WPF, Caliburn.Micro and Dapper. I have three combo boxes: the first one is for the region, the second one is for the provinces in the particular selected region and the third one are the cities in the particular selected province. What I want to achieve is that when I selected a particular region it will display all the provinces in that region, the same with the province combo box, when selected it will display all the cities associated with that province. Can this be done in a single method? Here is my code so far.
DataAccess
public List<RegionModel> GetRegion_All()
{
List<RegionModel> output;
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(GlobalConfig.CnnString(db)))
{
output = connection.Query<RegionModel>("dbo.spRegion_GetAll").ToList();
var p = new DynamicParameters();
foreach (RegionModel region in output)
{
p = new DynamicParameters();
p.Add("#RegionId", region.Id);
region.Provinces = connection.Query<ProvinceModel>("dbo.spProvince_ByRegion", p, commandType: CommandType.StoredProcedure).ToList();
foreach (ProvinceModel province in region.Provinces)
{
p = new DynamicParameters();
p.Add("#ProvinceId", province.Id);
region.Cities = connection.Query<CityModel>("dbo.spCity_ByProvince", p, commandType: CommandType.StoredProcedure).ToList();
}
}
}
return output;
}
Models
public class RegionModel
{
public int Id { get; set; }
public string Region { get; set; }
public string RegionName { get; set; }
public List<ProvinceModel> Provinces { get; set; } = new List<ProvinceModel>();
public List<CityModel> Cities { get; set; } = new List<CityModel>();
public List<BarangayModel> Barangays { get; set; } = new List<BarangayModel>();
}
public class ProvinceModel
{
public int Id { get; set; }
public string Province { get; set; }
public int RegionId { get; set; }
}
public class CityModel
{
public int Id { get; set; }
public string City { get; set; }
public int ProvinceId { get; set; }
public int ZipCode { get; set; }
}
ViewModel
public class ShellViewModel : Screen
{
private BindableCollection<RegionModel> _region;
private RegionModel _selectedRegion;
private ProvinceModel _selectedProvince;
public ShellViewModel()
{
GlobalConfig.InitializeConnections(DatabaseType.Sql);
Region = new BindableCollection<RegionModel>(GlobalConfig.Connection.GetRegion_All());
}
public BindableCollection<RegionModel> Region
{
get { return _region; }
set
{
_region = value;
}
}
public RegionModel SelectedRegion
{
get { return _selectedRegion; }
set
{
_selectedRegion = value;
NotifyOfPropertyChange(() => SelectedRegion);
}
}
public ProvinceModel SelectedProvince
{
get { return _selectedProvince; }
set
{
_selectedProvince = value;
NotifyOfPropertyChange(() => SelectedRegion);
}
}
View
<Window x:Class="WPFUI.Views.ShellView"
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:WPFUI.Views"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="ShellView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" x:Name="Region" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="1" x:Name="SelectedRegion_Provinces" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Province}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="2" x:Name="SelectedRegion_Cities" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding City}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Most of my codes ideas are from the tutorials I found in youtube, since references and materials for WPF, Caliburn.Micro and Dapper are very hard to find. Please be patient with my code :)
You have lot of mistake and you dont use the power of Caliburn
The wpf definition:
with Caliburn if you give name Region for combobox, it waits a bindableCollection with same name (Region) and SelectedItem is named SelectedRegion
(see name convention with Caliburn). So i choose Region, Province & City
In the different models i have renamed all strings RegionName, ProvinceName and CityName.
<ComboBox Grid.Row="0" x:Name="Region" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="1" x:Name="Province" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ProvinceName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="2" x:Name="City" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CityName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
then, i have modified your class definition:
public class RegionModel:PropertyChangedBase
{
public int Id { get; set; }
public string Region { get; set; }
public string RegionName { get; set; }
// each Region has its Provinces
public List<ProvinceModel> Provinces { get; set; } = new List<ProvinceModel>();
}
public class ProvinceModel:PropertyChangedBase
{
public int Id { get; set; }
public string ProvinceName { get; set; }
public int RegionId { get; set; }
// each Province has its Cities
public List<CityModel> Cities { get; set; } = new List<CityModel>();
}
public class CityModel:PropertyChangedBase
{
public int Id { get; set; }
public string CityName { get; set; }
public int ProvinceId { get; set; }
public int ZipCode { get; set; }
}
Then in ViewModel i add the different BindableCollection Region, Province & City dont forget to add
using Caliburn.Micro;
in the using definion. Then Add the Selected Definition
public class ShellViewModel : Screen
{
private RegionModel selectedRegion;
private ProvinceModel selectedProvince;
private CityModel selectedCity;
private BindableCollection<RegionModel> _region;
private BindableCollection<ProvinceModel> _province;
private BindableCollection<CityModel> _city;
public BindableCollection<RegionModel> Region
{
get { return _region; }
set
{
_region = value;
NotifyOfPropertyChange(() => Region);
}
}
public BindableCollection<ProvinceModel> Province
{
get { return _province; }
set
{
_province = value;
NotifyOfPropertyChange(() => Province);
}
}
public BindableCollection<CityModel> City
{
get { return _city; }
set
{
_city = value;
NotifyOfPropertyChange(() => City);
}
}
public RegionModel SelectedRegion
{
get { return selectedRegion; }
set
{
selectedRegion = value;
NotifyOfPropertyChange(() => SelectedRegion);
Province.Clear();
Province.AddRange(selectedRegion.Provinces);
NotifyOfPropertyChange(() => Province);
}
}
public ProvinceModel SelectedProvince
{
get { return selectedProvince; }
set
{
selectedProvince = value;
NotifyOfPropertyChange(() => SelectedProvince);
City.Clear();
City.AddRange(selectedProvince.Cities);
NotifyOfPropertyChange(() => City);
}
}
public CityModel SelectedCity
{
get { return selectedCity; }
set
{
selectedCity = value;
NotifyOfPropertyChange(() => SelectedCity);
}
}
public ShellViewModel()
{
// to DO INITIALIZE Regions
//
Province = new BindableCollection<ProvinceModel>();
City = new BindableCollection<CityModel>();
Region = new BindableCollection<RegionModel>(Regions);
}
and you have a functional sample
Each time you select a region, the associated provinces are loaded and same thing if you select a province for the associated Cities

Bind LINQ result to ComboBox [duplicate]

Quick question..
I have a List of objects of this class:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
}
And I want to link the List<Whatever> to a ComboxBox, where the user sees the Name of each Whatever object. How can I do that?
You could either use ComboBox.ItemTemplate like this:
C#:
List<Whatever> lst = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
cmb.ItemsSource = lst;
}
XAML:
<ComboBox Name="cmb">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Or use DisplayMemberPath:
<ComboBox Name="cmb" DisplayMemberPath="Name">
</ComboBox>
Or just override the ToString() function and it will do the job for you:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
public override string ToString()
{
return Name;
}
}
And then:
List<Whatever> MyList = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
MyComboBox.ItemsSource = MyList;
}
Create a Viewmodel:
public ObservableCollection<Whatever> WhCol
{
get { return this.Name; }
set { }
}
And then a Matching View
<ComboBox DisplayMemberPath="Name" ItemsSource="{Binding WhCol}" />
According to Model-View-Modelview Pattern
This is more suited if you wan't to make changes based on user input. (Which is kinda rare for a combobox).

WPF MVVM hierarchy selected item

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.

Categories