I've got a listview with binding and it's not updating. Can somebody find the bug? Wish I had some money, because I would offer a reward.
In this screen cap, the window on the right (Active Dinosaur List) is NOT updating when the status of a particular dinosaur is changing (note that when you click on the dinosaur (in this case, Nancy) it shows, correctly, that her status is, "Moving to food" while the Active Dinosaur List is showing her still Resting:
Here's all the relevant code, starting with the XAML for the window:
<Window x:Class="DinosaurIsland.ActiveDinosaurList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DinosaurIsland"
Title="ActiveDinosaurList" Height="850" Width="245" WindowStyle="SingleBorderWindow" Icon="/DinosaurIsland;component/Icon1.ico" ResizeMode="NoResize" >
<Window.Resources>
<local:EnergyBarColorConverter x:Key="EnergyBarColorConverter"/>
<local:DinoStatusConverter x:Key="DinoStatusConverter"/>
<DataTemplate x:Key="DinosaurInfo">
<StackPanel Orientation="Vertical" >
<Label Name="DinosaurName" Margin="0,0,0,-8" Content="{Binding Path=PersonalName}"/>
<Label Name="DinosaurSpecies" Margin="0,0,0,-8" FontStyle="Italic" Content="{Binding Path=Specie}"/>
<Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter}}"/>
<Label HorizontalAlignment="Center" Margin="0,0,0,-2" Content="Energy" />
<ProgressBar Name="Health" Margin="0,0,0,10" HorizontalAlignment="Center" VerticalAlignment="Top" Width="160" Height="15"
Foreground ="{Binding Path=Health, Converter={StaticResource EnergyBarColorConverter}}" Value="{Binding Path=Health}" />
<Separator/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Width="210">
<ListView x:Name="DinoListView" Width="207" ItemsSource="{Binding Path=Dinosaurs}" HorizontalAlignment="Left" Margin="3,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="Select Dinosaur" CellTemplate="{StaticResource DinosaurInfo}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
Here's the Dinosaur class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class Dinosaur : INotifyPropertyChanged
{
public string _specie;
public string Specie
{
get { return _specie; }
set{_specie = value; RaisePropertyChanged("Specie");}
}
public int Age { get; set; }
public int Weight { get; set; }
public double Height { get; set; }
public int _health;
public int Health
{
get { return _health; }
set{_health = value; RaisePropertyChanged("Health");}
}
public double Water { get; set; }
public double FoodConsumed { get; set; }
public bool Sex { get; set; }
public string PersonalName { get; set; }
public System.Windows.Point Head = new System.Windows.Point();
public List<System.Windows.Point> Location { get; set; }
public double Length { get; set; }
public double Speed { get; set; }
public byte _state;
public byte State
{
get { return _state; }
set{_state = value; RaisePropertyChanged("State");}
}
public System.Windows.Point Goal = new System.Windows.Point();
public System.Windows.Point[] FoodLocation = new System.Windows.Point[5]; // The last five locations that the dino found food
public System.Windows.Point[] WaterLocation = new System.Windows.Point[5]; // The last five locations that the dino found water
// Constructor
public Dinosaur()
{
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
protected void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Here's the ViewModel class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
public void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
}
}
Here's how the window is invoked:
// This is a global
public ViewModel vm = new ViewModel();
// ....
// Instantiate window
ViewModel vm = new ViewModel();
DinoListDialogBox.DataContext = vm;
DinoListDialogBox.Show();
That should be all the pieces to the puzzle. What am I missing?
Thanks... and I'll name a dinosaur after you.
Ok having looked at your source could get a solution for your use-case. I do suggest checking into MVVM properly.As it stands right now, Your project goes against MVVM in quite a few areas as I mentioned in chat.
Putting that aside with your current implementation to get the Dinosaurs list to be in sync with the ActiveDinosaurList View, these are the changes I made:
MainWindow.xaml.cs:
1) Switch Dinosaurs to an ObservableCollection<T> and a property. Such as
public static List<Dinosaur> Dinosaurs = new List<Dinosaur>();
to
public static ObservableCollection<Dinosaur> Dinosaurs { get; set; }
2) Add a static constructor to the MainWindow class to initialize the Dinosaurs property
static MainWindow() {
Dinosaurs = new ObservableCollection<Dinosaur>();
}
ViewModel.cs
3) Switch the Dinosaurs property to be a pass-thru to the static property in MainWindow and remove the backing collection. Such as
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
to
public ObservableCollection<Dinosaur> Dinosaurs {
get {
return MainWindow.Dinosaurs;
}
}
4) Finally add a hook to listen to CollectionChanged on MainWindow.Dinosaurs from ViewModel and RaisePropertyChanged on it's Dinosaurs property.
so switch:
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
to
public ViewModel() {
MainWindow.Dinosaurs.CollectionChanged += (sender, args) => RaisePropertyChanged("Dinosaurs");
}
That's it. Running your simulation now, When I forwarded the time, I could see the Status on the ActiveDinosaurs list getting updated fine.
Inside your bindings use UpdateSourceTrigger=PropertyChanged.
So your label would look like this: <Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter} UpdateSourceTrigger=PropertyChanged}" />.
Related
Class Diagram
I am developing a wpf application using MVVM pattern.I want to update Second datagrid based on selection of first datagrid & if there is any change in Itemsource of second datagrid I want to update that change while the selection of first datarid is retained. Can anybody help me with this.
The need is more or less similar to this DataGrid SelectionChanged MVVM. But Whenever there is a update in the first datagrid collection automatically the data in second datagrid must be updated for the selected item of first datagrid.
O8Lwl.png
Based on your comments I've created an minimal example which shows the binding. I hope this is what you were looking for.
MainWindow.xaml and code behind
<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"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="300*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<DataGrid Grid.Column="0" Grid.Row="0" Width="Auto" ItemsSource="{Binding DeviceList}" SelectedItem="{Binding SelectedDevice}" AutoGenerateColumns="True" />
<DataGrid Grid.Column="1" Grid.Row="0" Width="Auto" DataContext="{Binding SelectedDevice}" ItemsSource="{Binding Path=FaultList}" AutoGenerateColumns="True"/>
<Button Content="Trigger DataGrid 1 update" Grid.Column="0" Grid.Row="1" Margin="10,10,10,10" Width="Auto" Height="Auto" Click="Button_Click"/>
</Grid>
</Window>
// Code behind in MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.AddDevice();
}
}
}
MainViewModel and MainModel
using System;
using System.Collections.ObjectModel;
using System.Linq;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainViewModel : ViewModelBase<MainModel>
{
private DeviceViewModel selectedDevice;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return Model.DeviceList; }
}
public DeviceViewModel SelectedDevice
{
get { return selectedDevice; }
set
{
selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
public MainViewModel() : base(new MainModel())
{
}
public void AddDevice()
{
int rnd = new Random().Next(1, 100);
if (!Model.DeviceList.Any(x => x.Name == $"Device_{rnd}"))
Model.DeviceList.Add(new DeviceViewModel($"Device_{rnd}"));
RaisePropertyChanged("DeviceList");
}
}
}
using System.Collections.ObjectModel;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainModel
{
private ObservableCollection<DeviceViewModel> deviceList;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return deviceList; }
set { deviceList = value; }
}
public MainModel()
{
deviceList = new ObservableCollection<DeviceViewModel>();
}
}
}
DeviceViewModel.cs and Device.cs
using System.Collections.ObjectModel;
using WpfApp1.Models;
namespace WpfApp1.ViewModels
{
public class DeviceViewModel : ViewModelBase<Device>
{
private Fault selectedFault = null;
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
RaisePropertyChanged("Name");
}
}
public string SerialNumber
{
get { return Model.Id.ToString(); }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return Model.FaultList; }
}
public Fault SelectedFault
{
get { return selectedFault; }
set
{
selectedFault = value;
RaisePropertyChanged("SelectedFault");
}
}
public DeviceViewModel() : base(new Device())
{
FaultList.CollectionChanged += FaultList_CollectionChanged;
}
public DeviceViewModel(string name) : this()
{
Name = name;
for (int i = 0; i < 5; i++)
Model.FaultList.Add(new FaultViewModel() { Name = $"Fault_{i} of {name}" });
RaisePropertyChanged("FaultList");
}
private void FaultList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("FaultList");
}
}
}
using System;
using System.Collections.ObjectModel;
namespace WpfApp1.Models
{
public class Device
{
private string name = "";
private Guid id;
private ObservableCollection<FaultViewModel> faultList;
public string Name
{
get { return name; }
set { name = value; }
}
public Guid Id
{
get { return id; }
set { id = value; }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return faultList; }
set { faultList = value; }
}
public Device()
{
this.id = new Guid();
this.faultList = new ObservableCollection<FaultViewModel>();
}
public Device(string name) : this()
{
this.name = name;
}
}
}
FaultViewModel.cs and Fault.cs
using WpfApp1.Models;
namespace WpfApp1
{
public class FaultViewModel : ViewModelBase<Fault>
{
public string Name
{
get { return Model.FaultName; }
set
{
Model.FaultName = value;
RaisePropertyChanged("Name");
}
}
public string Id
{
get { return Model.FaultId.ToString(); }
}
public FaultViewModel() : base(new Fault())
{
}
}
}
using System;
namespace WpfApp1.Models
{
public class Fault
{
private Guid faultId;
private string faultName;
public Guid FaultId
{
get { return faultId; }
set { faultId = value; }
}
public string FaultName
{
get { return faultName; }
set { faultName = value; }
}
public Fault()
{
this.faultId = new Guid();
}
}
}
Last but not least: ViewModelBase.cs
using System.ComponentModel;
namespace WpfApp1
{
public class ViewModelBase<T> : INotifyPropertyChanged
{
T model;
public T Model { get { return model; } }
public ViewModelBase(T model)
{
this.model = model;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null && !string.IsNullOrEmpty(propertyName))
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you run the app you can click the button which simulates the update of device list in code behind. You then can select a device and the second DataGrid will show the FaultList of this device.
Old answer
Update: Related to your comments and the class diagram:
First thing:
I can only see one List in class Device. I assume this is the source for the first DataGrid? So you want to display properties of objects of type Fault in the 1st DataGrid. If so, where is the source for the second DataGrid? Or are you missing a Collection property in class Fault?
Second:
For data bindings you have to use ObservableCollection which implements INotifyCollectionChanged. You can not use a List<>.
Third:
Without seeing your code I can only guess what's going wrong. Let's assume class Device contains an ObservableCollection<Fault> FaultList and class Fault contains an ObservableCollection<string> FaultDetails. DataGrid 1 displays the list of faults and if you select one of them, DataGrid 2 displays some fault details. In your DeviceViewModel you would have the ObservableCollection<Fault> FaultList and a property Fault SelectedFault. Now FaultList has to be the ItemSource of the 1st DataGrid and SelectedFault has to be bound to the DataGrid.SelectedItem property. The ItemSource of Datagrid 2 has to be FaultDetails and the DataContext has to be SelectedFault. Maybe you need to propagate the change of the property.
I have not tested this! A minimal executable example that shows the problem would be great.
Old answer:
It's been a while since i wrote my last WPF application, but it could be that you either raise the NotifyPropertyChanged event for the 2nd DataGrid at the wrong place, or you raise it for the wrong Property.
DataGrid has a SelectionChanged event. Maybe you are able to access your VM from there and raise the correct PropertyChanged event for your 2nd DataGrid.
I'd like to access ComboBox items (which are defined in another class) in MainWindow.xaml.cs, but I can't.
I'm new to C# and WPF. The purpose of this code is to get a selected ComboBox item as a search key. I have copied from many example codes on the Internet and now I'm completely lost. I don't even know which part is wrong. So, let me show the entire codes (sorry):
MainWindow.xaml:
<Window x:Class="XY.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:XY"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="454.4">
<Grid>
<DataGrid ItemsSource="{Binding channels}"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Margin="0,0,0,-0.2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding name}"
Header="Channel" Width="Auto"/>
<DataGridTemplateColumn Width="100" Header="Point Setting">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="piontsComboBox"
ItemsSource="{Binding DataContext.points,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectionChanged="PrintText"
DisplayMemberPath="name"
SelectedValuePath="name"
Margin="5"
SelectedItem="{Binding DataContext.SelectedPoint,
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox x:Name="tb" Width="140" Height="30" Margin="10,250,200,30"></TextBox>
<Button x:Name="Browse_Button" Content="Browse" Margin="169,255,129.6,0"
Width="75" Click="Browse_Button_Click" Height="30" VerticalAlignment="Top"/>
</Grid>
MainWindow.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace XY
{
public partial class MainWindow : Window
{
public GridModel gridModel { get; set; }
public MainWindow()
{
InitializeComponent();
gridModel = new GridModel();
this.DataContext = gridModel;
}
private void Browse_Button_Click(object sender, RoutedEventArgs e)
{
WakeupClass clsWakeup = new WakeupClass();
clsWakeup.BrowseFile += new EventHandler(gridModel.ExcelFileOpen);
clsWakeup.Start();
}
void PrintText(object sender, SelectionChangedEventArgs args)
{
//var item = pointsComboBox SelectedItem as Point;
//if(item != null)
//{
// tb.Text = "You selected " + item.name + ".";
//}
MessageBox.Show("I'd like to show the item.name in the TextBox.");
}
}
public class WakeupClass
{
public event EventHandler BrowseFile;
public void Start()
{
BrowseFile(this, EventArgs.Empty);
}
}
}
Point.cs:
namespace XY
{
public class Point : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _code;
public int code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged("code");
}
}
}
}
Record.cs:
namespace XY
{
public class Record : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _PointCode;
public int PointCode
{
get { return _PointCode; }
set
{
_PointCode = value;
OnPropertyChanged("PointCode");
}
}
private Record _selectedRow;
public Record selectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
private Point _selectedPoint;
public Point SelectedPoint
{
get { return _selectedPoint; }
set
{
_selectedPoint = value;
_selectedRow.PointCode = _selectedPoint.code;
OnPropertyChanged("SelectedRow");
}
}
}
}
ViewModelBase.cs:
using System.ComponentModel;
namespace XY
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
GridModel.cs:
using System.Collections.ObjectModel;
using System.Windows;
namespace XY
{
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> channels { get; set; }
public ObservableCollection<Point> points { get; set; }
public GridModel()
{
channels = new ObservableCollection<Record>() {
new Record {name = "High"},
new Record {name = "Middle"},
new Record {name = "Low"}
};
}
internal void ExcelFileOpen(object sender, System.EventArgs e)
{
points = new ObservableCollection<Point> { new Point { } };
points.Add(new Point { name = "point1", code = 1 });
points.Add(new Point { name = "point2", code = 2 });
points.Add(new Point { name = "point3", code = 3 });
MessageBox.Show("Assume that Excel data are loaded here.");
}
}
}
The procedure goes like:
Click on the "Browse" button to load the data.
Click on the 1st column "Channel" to sort the list (This is a bug, but if you don't, the "Point Setting" items won't show up).
Click on the "Point Setting" ComboBox to select the items (point1, point2, ..., etc.).
This code is supposed to show the selected item name in the TextBox.
If everything is in MainWindow.xaml.cs, the ComboBox items could be accessed. Since I split the codes into different classes, it has not been working. Please help me. Any suggestion would be helpful.
Your binding does work. You can make use of the sender object to achieve what you wanted.
void PrintText(object sender, SelectionChangedEventArgs args)
{
var comboBox = sender as ComboBox;
var selectedPoint = comboBox.SelectedItem as Point;
tb.Text = selectedPoint.name;
}
The problem is that the DataGridColumn is not part of the WPF logical tree and so your relative source binding will not work. The only way to get your binding to work is a type of kluge (very common with WPF in my experience). Create a dummy element that is in the logical tree and then reference that.
So
<FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
<DataGrid ItemsSource="{Binding channels}"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Margin="0,0,0,-0.2">
Then your binding will look like this
<ComboBox x:Name="piontsComboBox"
ItemsSource="{Binding DataContext.points,
Source={x:Reference dummyElement}}"
SelectionChanged="PrintText"
DisplayMemberPath="name"
SelectedValuePath="name"
Margin="5"
SelectedItem="{Binding DataContext.SelectedPoint,
Source={x:Reference dummyElement},
Mode=TwoWay}"/>
The 2nd column items "Point Setting" don't show up until the 1st column items are sorted, clicking on the header of the 1st column. The goal of this code is to link the 1st and 2nd column items, then use the 2nd column items as the search keys.
I'm new to C# and WPF.
I tired to put sequential numbers in front of the 1st column items (1., 2., and so on) because I thought it would solve the problem if those items are initially sorted. But, no luck. I heard that ObservableCollection<> doesn't manage the input order, so once I changed it with List<>. But it didn't solve this problem, too.
Actually, I don't want to sort the 1st column; they should be fixed and no need to change the order/number at all.
To avoid any confusions, let me show my entire codes (sorry).
MainWindow.xaml:
<Window x:Class="XY.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:XY"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="454.4">
<Grid>
<DataGrid ItemsSource="{Binding channels}"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Margin="0,0,0,-0.2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding name}"
Header="Channel" Width="Auto"/>
<DataGridTemplateColumn Width="100" Header="Point Setting">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="piontsComboBox"
ItemsSource="{Binding DataContext.points,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectionChanged="PrintText"
DisplayMemberPath="name"
SelectedValuePath="name"
Margin="5"
SelectedItem="{Binding DataContext.SelectedPoint,
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox x:Name="tb" Width="140" Height="30" Margin="10,250,200,30"></TextBox>
<Button x:Name="Browse_Button" Content="Browse" Margin="169,255,129.6,0"
Width="75" Click="Browse_Button_Click" Height="30" VerticalAlignment="Top"/>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace XY
{
public partial class MainWindow : Window
{
public GridModel gridModel { get; set; }
public MainWindow()
{
InitializeComponent();
gridModel = new GridModel();
this.DataContext = gridModel;
}
private void Browse_Button_Click(object sender, RoutedEventArgs e)
{
WakeupClass clsWakeup = new WakeupClass();
clsWakeup.BrowseFile += new EventHandler(gridModel.ExcelFileOpen);
clsWakeup.Start();
}
void PrintText(object sender, SelectionChangedEventArgs args)
{
var comboBox = sender as ComboBox;
var selectedPoint = comboBox.SelectedItem as Point;
tb.Text = selectedPoint.name;
}
}
public class WakeupClass
{
public event EventHandler BrowseFile;
public void Start()
{
BrowseFile(this, EventArgs.Empty);
}
}
}
ViewModelBase.cs:
using System.ComponentModel;
namespace XY
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
Point.cs:
namespace XY
{
public class Point : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _code;
public int code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged("code");
}
}
}
}
Record.cs:
namespace XY
{
public class Record : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _PointCode;
public int PointCode
{
get { return _PointCode; }
set
{
_PointCode = value;
OnPropertyChanged("PointCode");
}
}
private Record _selectedRow;
public Record selectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
private Point _selectedPoint;
public Point SelectedPoint
{
get { return _selectedPoint; }
set
{
_selectedPoint = value;
_selectedRow.PointCode = _selectedPoint.code;
OnPropertyChanged("SelectedRow");
}
}
}
}
GridModel.cs:
using System.Collections.ObjectModel;
using System.Windows;
namespace XY
{
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> channels { get; set; }
public ObservableCollection<Point> points { get; set; }
public GridModel()
{
channels = new ObservableCollection<Record>() {
new Record {name = "1. High"},
new Record {name = "2. Middle"},
new Record {name = "3. Low"}
};
}
internal void ExcelFileOpen(object sender, System.EventArgs e)
{
points = new ObservableCollection<Point> { new Point { } };
MessageBox.Show("Please assume that Excel data are loaded here.");
points.Add(new Point { name = "point1", code = 1 });
points.Add(new Point { name = "point2", code = 2 });
points.Add(new Point { name = "point3", code = 3 });
points.Add(new Point { name = "point4", code = 4 });
}
}
}
The procedure goes like:
Click on the "Browse" button to load the data.
Click on the 1st column "Channel" to sort the list (GOAL: I'd like to GET RID OF this step).
Click on the "Point Setting" ComboBox to select the items (point1, point2, ..., etc.).
... I don't know if ObservableCollection<> is appropriate here. If List<> or any other type is better, please change it. Any suggestion would be helpful. Thank you in advance.
Change your points ObservableCollection like such, because you're setting the reference of the collection after the UI is rendered, you would need to trigger the PropertyChanged event to update the UI.
private ObservableCollection<Point> _points;
public ObservableCollection<Point> points
{
get { return _points; }
set
{
_points = value;
OnPropertyChanged(nameof(points));
}
}
An alternative would be to first initialise your collection.
public ObservableCollection<Point> points { get; set; } = new ObservableCollection<Point>();
internal void ExcelFileOpen(object sender, System.EventArgs e)
{
// Do not re-initialise the collection anymore.
//points = new ObservableCollection<Point> { new Point { } };
points.Add(new Point { name = "point1", code = 1 });
points.Add(new Point { name = "point2", code = 2 });
points.Add(new Point { name = "point3", code = 3 });
}
I'm trying to filter by a sub property of the items (Processor objects) in my collection but am unable. The property is a simple Boolean to indicate if the item is enabled or disabled. Enabled items in the list should be listed in the other listbox found on the first tab (enabled processors).
Below is a full working example that can be pasted in a starter WPF application
If you uncomment this line
_selectedProcessorsView.Filter = processorFilter;
in the MainWindow_VM() constructor, the processors are no longer displayed in the configuration tab.
Two main questions:
Why does the filtering affect both lists?
How can I fix this so that the enabled processors listbox on the first tab is bound to only the checked items in the second tab?
NOTE: The three classes at the end of my code (Config, Settings, and Processor are part of a class library that I can't modify. The WPF app is just a UI around that library.
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindow_VM></local:MainWindow_VM>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="enabled processors">
<ListBox ItemsSource="{Binding selectedProcessorsView}" VerticalAlignment="Top" FontSize="14">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=processorType}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
<TabItem Header="configuration">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}" SelectedItem="{Binding selectedConfiguration}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" ItemsSource="{Binding reportProcessors}" SelectedItem="{Binding selectedProcessor}">
<ListView.View>
<GridView>
<GridViewColumn Header="Enabled" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Settings.isEnabled}" ></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Processor" DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding Path=processorId, Mode=OneWay}" >
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 1", new Processor() { processorType = "Blue" } },
{ "Processor 2", new Processor() { processorType = "Yellow" } }
}
});
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 3", new Processor() { processorType = "Green" } },
{ "Processor 4", new Processor() { processorType = "Red" } }
}
});
_selectedProcessorsView = CollectionViewSource.GetDefaultView(reportProcessors);
//_selectedProcessorsView.Filter = processorFilter;
}
public bool processorFilter(object item)
{
bool result = true;
Processor p = item as Processor;
if (p.Settings.isEnabled == false)
result = false;
return result;
}
private Config _selectedConfiguration;
public Config selectedConfiguration
{
get { return _selectedConfiguration; }
set
{
SetProperty(ref _selectedConfiguration, value);
reportProcessors.Clear();
foreach (KeyValuePair<string, Processor> kvp in value.Processors)
reportProcessors.Add(kvp.Value);
}
}
public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();
public ObservableCollection<Processor> reportProcessors { get; } = new ObservableCollection<Processor>();
private ICollectionView _selectedProcessorsView;
public ICollectionView selectedProcessorsView
{
get { return _selectedProcessorsView; }
}
}
///These three classes below are part of a separate class library that I cannot modify
public class Config
{
public string id { get; set; }
public Dictionary<string, Processor> Processors { get; set; }
}
public class Processor
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings() {
isEnabled = false
};
}
public class Settings
{
public bool isEnabled { get; set; }
}
}
Why does the filtering affect both lists?
{Binding reportProcessors} and {Binding selectedProcessorsView} bind to the same view. When you bind to an ObservableCollection<T>, or any other type of source collection for that matter, you are actually binding to an automatically generated CollectionView and not to the actual source collection itself.
How can I fix this so that the enabled processors listbox on the first tab is bound to only the checked items in the second tab?
Bind to two different views. But you also need to notify the view when the isEnabled propery is actually set to a new value. Otherwise your filter won't be updated as you check the CheckBoxes. The fact that you are using a Setting class complicates this a bit but you could add a property to the Processor class and live filter on this one. Both Settings and Processor need to implement the INotifyPropertyChanged event.
Please refer to the following sample code. It should give you the idea.
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
...
ListCollectionView selectedProcessorsView = new ListCollectionView(reportProcessors);
selectedProcessorsView.LiveFilteringProperties.Add(nameof(Processor.IsEnabled));
selectedProcessorsView.IsLiveFiltering = true;
selectedProcessorsView.Filter = processorFilter;
_selectedProcessorsView = selectedProcessorsView;
}
...
}
public class Processor : INotifyPropertyChanged
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings()
{
isEnabled = false
};
public bool IsEnabled => Settings.isEnabled;
public Processor()
{
Settings.PropertyChanged += Settings_PropertyChanged;
}
private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(IsEnabled));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Settings : INotifyPropertyChanged
{
private bool _isEnabled;
public bool isEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
In the end I went with the following approach, combined mm8's answer to my first question, with my own wrapper class that acts as a viewmodel to the Processor object (model).
Whenever a checkbox is checked or unchecked, the listviewcollection is refreshed thus updating the collection displayed in the first tab.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 1", new Processor() { processorType = "Blue" } },
{ "Processor 2", new Processor() { processorType = "Yellow" } }
}
});
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 3", new Processor() { processorType = "Green" } },
{ "Processor 4", new Processor() { processorType = "Red" } }
}
});
selectedProcessorsView = new ListCollectionView(processors);
selectedProcessorsView.Filter = processorFilter;
}
public bool processorFilter(object item)
{
bool result = true;
ProcessorWrapper p = item as ProcessorWrapper;
if (p.isEnabled == false)
result = false;
return result;
}
private Config _selectedConfiguration;
public Config selectedConfiguration
{
get { return _selectedConfiguration; }
set
{
SetProperty(ref _selectedConfiguration, value);
processors.Clear();
foreach (KeyValuePair<string, Processor> kvp in value.Processors)
processors.Add(new ProcessorWrapper(kvp.Value, this.selectedProcessorsView));
}
}
public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();
public ObservableCollection<ProcessorWrapper> processors { get; } = new ObservableCollection<ProcessorWrapper>();
public ListCollectionView selectedProcessorsView { get; set; }
}
public class ProcessorWrapper : BindableBase
{
public ProcessorWrapper(Processor processor, ListCollectionView lcv)
{
_processor = processor;
_lcv = lcv;
}
private Processor _processor;
private ListCollectionView _lcv;
private string _processorType;
public string processorType
{
get {
_processorType = _processor.processorType;
return _processorType;
}
}
private bool _isEnabled;
public bool isEnabled
{
get {
_isEnabled = this._processor.Settings.isEnabled;
return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
this._processor.Settings.isEnabled = _isEnabled;
_lcv.Refresh();
}
}
}
/// <summary>
/// These classes belong to a separate class library, should not be modified.
/// </summary>
public class Config
{
public string id { get; set; }
public Dictionary<string, Processor> Processors { get; set; }
}
public class Processor
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings() {
isEnabled = false
};
}
public class Settings
{
public bool isEnabled { get; set; }
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindow_VM></local:MainWindow_VM>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="enabled processors">
<ListBox ItemsSource="{Binding selectedProcessorsView}" VerticalAlignment="Top" FontSize="14">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=processorType}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
<TabItem Header="configuration">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}" SelectedItem="{Binding selectedConfiguration}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" ItemsSource="{Binding processors}" SelectedItem="{Binding selectedProcessor}">
<ListView.View>
<GridView>
<GridViewColumn Header="Enabled" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=isEnabled}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Processor" DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
Having some problems displaying strings in a datagrid.
To explain the code: I am binding a collection of Soldiers to a ComboBox. A Soldier has its own collection of weapons.
When I select a specific soldier in the ComboBox, I want that soldier's weapons displayed in the datagrid. I believe I'm binding correctly, but the datagrid always comes up blank. Anybody know what i'm doing wrong?
XAML
<Grid>
<ComboBox x:Name="Character_ComboBox" HorizontalAlignment="Left" VerticalAlignment="Top" Width="328" Height="25">
</ComboBox>
</Grid>
<DataGrid x:Name="Character_items_datagrid" ItemsSource="{Binding ElementName=Character_ComboBox, Path= SelectedItem.Equipment, Mode=OneWay}" Margin="328,0,0,0" Grid.RowSpan="2" >
<DataGrid.Columns>
<DataGridTextColumn Header="Primary" Binding="{Binding Primary, Mode=TwoWay}" FontWeight="Bold" Foreground="Black" Width="0.1*"></DataGridTextColumn>
<DataGridTextColumn Header ="Secondary" Binding="{Binding Secondary, Mode=TwoWay}" Width="0.1*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Soldier Class
public class Soldier
{
public string Soldier_Class { get; set; }
public ObservableCollection<Weapons> Equipment { get; set; }
}
Weapons Class
public class Weapons
{
string Primary { get; set; }
string Secondary { get; set; }
public Weapons(string primary, string secondary)
{
this.Primary = primary;
this.Secondary = secondary;
}
}
MainWindow
public ObservableCollection<Soldier> squad_members = new ObservableCollection<Soldier>();
public MainWindow()
{
InitializeComponent();
squad_members.Add(new Soldier() { Soldier_Class = "Assult Soldier", Equipment = new ObservableCollection<Weapons>() { new Weapons("M4 Rifle", "Compact 45 Pistol")}});
squad_members.Add(new Soldier() { Soldier_Class = "SMG Soldier", Equipment = new ObservableCollection<Weapons>() { new Weapons("RPK Machine Gun", "HK Shotgun"), new Weapons("SAW Machine Gun", "Compact 45 Pistol")}});
squad_members.Add(new Soldier() { Soldier_Class = "Juggernaut", Equipment = new ObservableCollection<Weapons>() { new Weapons("MP5", "Bowie Knife") }});
Binding comboBinding = new Binding();
comboBinding.Source = squad_members;
BindingOperations.SetBinding(Character_ComboBox, ComboBox.ItemsSourceProperty, comboBinding);
Character_ComboBox.DisplayMemberPath = "Soldier_Class";
Character_ComboBox.SelectedValuePath = "Soldier_Class";
}
Result:
You need to make properties in the model public for binding to be able to work :
public class Weapons
{
public string Primary { get; set; }
public string Secondary { get; set; }
.....
}
Your DataGrid looks populated with items correctly, just the properties of each item are not correctly displayed in the columns. This is indication that binding engine can't access the item's properties due to it's private accessibility.
Your primary problem is the public access modifier, as har07 wrote.
There are a lot of other things you can improve as well. Implement INotifyPropertyChanged for your classes, so any change to the properties is immediately reflected by the UI. Without compelling reasons, do not create bindings in code. Use a ViewModel to bind to, instead of binding directly to elements like ComboBox.SelectedItem. Set AutoGenerateColumns to false if you want to style your columns (your code would produce four columns). Use Grid.ColumnDefinitions instead of assigning a fixed margin.
Models:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class SquadViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Soldier> _squadMembers;
public ObservableCollection<Soldier> SquadMembers { get { return _squadMembers; } set { _squadMembers = value; OnPropertyChanged("SquadMembers"); } }
private Soldier _selectedSoldier;
public Soldier SelectedSoldier { get { return _selectedSoldier; } set { _selectedSoldier = value; OnPropertyChanged("SelectedSoldier"); } }
public SquadViewModel()
{
SquadMembers = new ObservableCollection<Soldier>()
{
new Soldier() { SoldierClass = "Assult Soldier", Equipment = new ObservableCollection<Weapon>() { new Weapon("M4 Rifle", "Compact 45 Pistol") } },
new Soldier() { SoldierClass = "SMG Soldier", Equipment = new ObservableCollection<Weapon>() { new Weapon("RPK Machine Gun", "HK Shotgun"), new Weapon("SAW Machine Gun", "Compact 45 Pistol") } },
new Soldier() { SoldierClass = "Juggernaut", Equipment = new ObservableCollection<Weapon>() { new Weapon("MP5", "Bowie Knife") } }
};
}
}
public class Soldier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _soldierClass;
public string SoldierClass { get { return _soldierClass; } set { _soldierClass = value; OnPropertyChanged("SoldierClass"); } }
private ObservableCollection<Weapon> _equipment;
public ObservableCollection<Weapon> Equipment { get { return _equipment; } set { _equipment = value; OnPropertyChanged("Equipment"); } }
}
public class Weapon : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _primary;
string Primary { get { return _primary; } set { _primary = value; OnPropertyChanged("Primary"); } }
private string _secondary;
string Secondary { get { return _secondary; } set { _secondary = value; OnPropertyChanged("Secondary"); } }
public Weapon(string primary, string secondary)
{
this.Primary = primary;
this.Secondary = secondary;
}
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
Title="MainWindow" Height="350" Width="580">
<Window.DataContext>
<vm:SquadViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="CbxCharacter" HorizontalAlignment="Left" VerticalAlignment="Top" Width="328" Height="25"
ItemsSource="{Binding SquadMembers}" SelectedItem="{Binding SelectedSoldier}"
DisplayMemberPath="SoldierClass" SelectedValuePath="SoldierClass"/>
<DataGrid x:Name="DgCharacterItems" ItemsSource="{Binding SelectedSoldier.Equipment, Mode=OneWay}" Grid.Column="1" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Primary" Binding="{Binding Primary, Mode=TwoWay}" FontWeight="Bold" Foreground="Black" Width="*" />
<DataGridTextColumn Header="Secondary" Binding="{Binding Secondary, Mode=TwoWay}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>