How to get View to update when the ViewModel gets changed - c#

I'm building a WPF application to manage a student database stored in a SharePoint list. I'm new to using MVVM and have gone through a couple tutorials. I have gotten as far as having successfully created a view and model and have managed to bind it to a datagrid control. What I would like to do is to update the data in the view based on the output of a combobox.
Here's my model:
using System.ComponentModel;
namespace StudentManagement.Model
{
public class Student : INotifyPropertyChanged
{
private string _Title;
private string _FullName;
public string Title
{
get { return _Title; }
set
{
if (_Title != value)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
public string FullName
{
get { return _FullName; }
set
{
if (_FullName != value)
{
_FullName = value;
RaisePropertyChanged("FullName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Here's the viewmodel:
using System.Collections.ObjectModel;
using StudentManagement.Model;
using SP = Microsoft.SharePoint.Client;
using StudentManagement.publicClasses;
namespace StudentManagement.ViewModel
{
public class StudentViewModel
{
public ObservableCollection<Student> Students { get; set; }
public void LoadStudents(string query)
{
ObservableCollection<Student> _students = new
ObservableCollection<Student>();
SP.ClientContext ctx = clientContext._clientContext;
SP.CamlQuery qry = new SP.CamlQuery();
qry.ViewXml = query;
SP.ListItemCollection splStudents =
ctx.Web.Lists.GetByTitle("Students").GetItems(qry);
ctx.Load(splStudents);
ctx.ExecuteQuery();
foreach (SP.ListItem s in splStudents)
{
_students.Add(new Student { Title = (string)s["Title"], FullName = (string)s["FullName"] });
}
Students = _students;
}
}
}
Here's my XAML
<UserControl x:Class="StudentManagement.Views.StudentsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:StudentManagement.Views" >
<Grid>
<StackPanel HorizontalAlignment="Left" Width="200" >
<TextBox Name="txtSearch" AcceptsReturn="True" ></TextBox>
<ComboBox Name="cmbStatus" SelectionChanged="cmbStatus_SelectionChanged" SelectedIndex="0">
<ComboBoxItem>Active</ComboBoxItem>
<ComboBoxItem>Inquiring</ComboBoxItem>
<ComboBoxItem>Inactive</ComboBoxItem>
<ComboBoxItem>Monitoring</ComboBoxItem>
</ComboBox>
<DataGrid Name="dgStudentList" ItemsSource="{Binding Path=Students}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
<DataGridTextColumn Header="Parent" Binding="{Binding FullName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
</UserControl>
...and the code behind for the view:
using System.Windows.Controls;
using StudentManagement.ViewModel;
namespace StudentManagement.Views
{
/// <summary>
/// Interaction logic for StudentsView.xaml
/// </summary>
public partial class StudentsView : UserControl
{
private StudentViewModel _viewModel = new
StudentViewModel();
public StudentsView()
{
InitializeComponent();
DataContext = _viewModel;
}
private void cmbStatus_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string combotext = ((sender as ComboBox).SelectedItem as ComboBoxItem).Content as string;
string qry = #"<View>
<Query>
<Where><Eq><FieldRef Name='Current_x0020_Status' /><Value Type='Choice'>" + combotext + #"</Value></Eq></Where>
</Query>
</View>";
_viewModel.LoadStudents(qry);
}
}
}
As it stands now, the students get loaded into the datagrid on load just fine. When the event cmbStatus_SelectionChanged fires i've done tests and I can see that the LoadStudents function fires and returns the correct number of entries, but nothing gets updated on the datagrid.
I'm sure this a noob mistake and I'm missing something basic but this one is doing my head in and I'd appreciate any guidance.

Since StudentViewModel.LoadStudents() changes the value of the Students property, the view model needs to notify the view that this changed. You can do this by having StudentViewModel implement INotifyPropertyChanged (just like Student does). The DataGrid will subscribe to the PropertyChanged event, and will update its contents when that event is fired.

You are initializing your Students collection every time if the ComboBox's selection changed.
ObservableCollection<Student> _students = new ObservableCollection<Student>();
You should not do this with a bound collection in ViewModel. You can clear the collection and add new items like this.
public class StudentViewModel
{
public ObservableCollection<Student> Students { get; set; } = new ObservableCollection<Student>();
public void LoadStudents(string query)
{
Students.Clear();
SP.ClientContext ctx = clientContext._clientContext;
SP.CamlQuery qry = new SP.CamlQuery();
qry.ViewXml = query;
SP.ListItemCollection splStudents = ctx.Web.Lists.GetByTitle("Students").GetItems(qry);
ctx.Load(splStudents);
ctx.ExecuteQuery();
foreach (SP.ListItem s in splStudents)
{
Students.Add(new Student { Title = (string)s["Title"], FullName = (string)s["FullName"] });
}
}
}

Related

Cannot get selected ComboBox items in MainWindow.xaml.cs

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}"/>

ComboBox items don't show up until the 1st column is sorted

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 });
}

How to use Static Resource for WPF datagrid comboBox binding

I am developing WPF application with entity framework as well. But I DO NOT use MVVM I have a ENUM types, So I need to initialize the combo box item source with ALL enum types and select the value based on my Data. to simplify just consider binding a simple list to combo box. I have tired different ways but there is a problem I cant figure out.
<Page x:Class="Library.View.Reader"
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:Library.View"
mc:Ignorable="d"
d:DesignHeight="300"
Title="Reader" Width="900">
<Grid Margin="0,0,0,0">
<DataGrid Name="grid_reader" AutoGenerateColumns="True" HorizontalAlignment="Left" Height="126" Margin="23,20,0,0" VerticalAlignment="Top" Width="845" RowEditEnding="grid_reader_RowEditEnding" AutoGeneratingColumn="grid_reader_AutoGeneratingColumn">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Type"
ItemsSource="{DynamicResource enumlist}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValueBinding="{Binding Type}"
</DataGrid.Columns>
</DataGrid>
</Grid>
I have tried DynamicResource ,StaticResource,Binding. None of them works!
public partial class Reader : Page
{
public Reader() // Redaer is my page in xaml
{
LibraryDataAccess.Model1 model = new Model1();
List<LibraryDataAccess.Model.Reader> list = new List<LibraryDataAccess.Model.Reader>();
list = model.Readers.ToList();
public ObservableCollection<ReaderType> enumlist { get; set; }
// initialize datagrid succefully Also enumlist = getEnumValues();
enumlist = new ObservableCollection<ReaderType>();
//enumlist = new List<LibraryDataAccess.EnumTypes.ReaderType>();
typelist = Enum.GetValues(typeof
(LibraryDataAccess.EnumTypes.ReaderType))
.Cast<LibraryDataAccess.EnumTypes.ReaderType>().Select(x => new ReaderType { Id = (int)x, Name = x.ToString() }).ToList();
foreach (var item in typelist)
{
enumlist.Add(item);
}
grid_reader.ItemsSource = list;
}
public class ReaderType
{
public int Id { get; set; }
public string Name { get; set; }
}
}
nothing is loaded into the Combo. What is the solution. Thanks
EDITED:
I am 99% sure that the problem is ItemSource of combo BUT:
I need the combo filled with enum values and the selected value is shown as given, suhc as Staff (which is in enum list with id 2) anyway the combo would not be filled at all. I am using this in a separate wpf Page.
I think the problem is in my data context related to combo box, i tried with sparated combo box even, with the binding mentioned above, but it does not work.
When I use AUTOGENERATED = true, the combo box would be created nicely with seleted value.
if i have undertant what you want (else i 'll destroy the answer): here is a solution using the datagrid with combobox using enumlist: i am using markupextension for that
your class.cs: i have implemented INotifyPropertyChanged if you want update data
using System.ComponentModel;
namespace zzWpfApp1
{
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ReaderType
{
[Description("Super Chief")] Chief,
[Description("Super Staff")] Staff,
[Description("super Officer")] Officer,
}
public class User : INotifyPropertyChanged
{
private string _name;
private ReaderType _readerType;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
public ReaderType ReaderType
{
get { return _readerType; }
set
{
_readerType = value;
NotifyPropertyChanged("ReaderType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
EnumConverter.cs: generic file to trap the description of enum file
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Markup;
namespace zzWpfApp1
{
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
public Type EnumType
{
get { return this._enumType; }
set
{
if (value != this._enumType)
{
if (null != value)
{
Type enumType = Nullable.GetUnderlyingType(value) ?? value;
if (!enumType.IsEnum)
throw new ArgumentException("Type must be for an Enum.");
}
this._enumType = value;
}
}
}
public EnumBindingSourceExtension(Type enumType)
{
this.EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (null == this._enumType)
throw new InvalidOperationException("The EnumType must be specified.");
Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
Array enumValues = Enum.GetValues(actualEnumType);
if (actualEnumType == this._enumType)
return enumValues;
Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
enumValues.CopyTo(tempArray, 1);
return tempArray;
}
}
public class EnumDescriptionTypeConverter : EnumConverter
{
public EnumDescriptionTypeConverter(Type type)
: base(type)
{
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if (destinationType == typeof(string))
{
if (value != null)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description)))
? attributes[0].Description
: value.ToString();
}
}
return string.Empty;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
---
mainwindow.xaml.cs:
namespace zzWpfApp1
{
public partial class MainWindow : Window
{
public ObservableCollection<User> Users { get; set; }
public MainWindow()
{
//Sample of different users
List<User> users = new List<User>();
users.Add(new User() { Name = "Donald Duck", ReaderType = ReaderType.Chief });
users.Add(new User() { Name = "Mimmi Mouse", ReaderType = ReaderType.Staff });
users.Add(new User() { Name = "Goofy", ReaderType = ReaderType.Officer });
Users = new ObservableCollection<User>(users);
InitializeComponent();
DataContext = this;
}
}
}
xaml file:
<DataGrid Name="grid_reader" AutoGenerateColumns="False" Margin="20,20,300,20" ItemsSource="{Binding Users}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridComboBoxColumn Header="ReaderType" MinWidth="150"
SelectedItemBinding="{Binding ReaderType}"
ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:ReaderType}}}"/>
</DataGrid.Columns>
Finally I found a better and simpler answer for my question, let me share with you guys:
We need to define a class as the dataSource of our combo box. So all of combo boxes must have these datas, and then based on a property which we set(from the data grids data source) combo box value must be selected and shown. Also we need to define a combo box resource at the top of the page, or window.
<Page.Resources>
<local:viewmodel x:Key="viewmodel"/>
</Page.Resources>
<Grid Margin="0,0,0,0">
<DataGrid Name="grid_doc" AutoGenerateColumns="True" HorizontalAlignment="Left" Height="100" Margin="31,55,0,0" VerticalAlignment="Top" Width="636">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="PublisherId"
ItemsSource="{StaticResource viewmodel}"
SelectedValueBinding="{Binding PublisherId , UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value"
SelectedValuePath="Key">
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
<ComboBox HorizontalAlignment="Left" Margin="130,193,0,0" VerticalAlignment="Top" Width="120"
ItemsSource="{StaticResource viewmodel}"
SelectedValue="{Binding PublisherId , UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value"
SelectedValuePath="Key">
</ComboBox>
</Grid>
NOTE here, I used Key and Value for selected and display members. its important, even key and value are wrong. it MUST be Value and Key, with uppercase.
I posted a simpler version, not enum members, it does not make any difference. you can create your list of enum members, and then add it into in your viewmodel class.It doesn't matter if your viewmodel class has other properties. just be sure to return an Enumerable data or inherit from a upper Enumerable class, as I did.
public partial class Document : Page
{
LibraryDataAccess.Model1 model;
List<LibraryDataAccess.Model.Document> list;
public Document()
{
model = new Model1();
list = new List<LibraryDataAccess.Model.Document>();
list = model.Documents.ToList();
InitializeComponent();
list.Add(new LibraryDataAccess.Model.Document { Id = 1, PublisherId = 2, Title = "sdfs" });
grid_doc.ItemsSource = list;
}
public class viewmodel : List<KeyValuePair<string,string>>
{
public viewmodel()
{
this.Add(new KeyValuePair<string, string>(1.ToString(), "s"));
this.Add(new KeyValuePair<string, string>(2.ToString(), "t"));
}
}
Thanks to previous answer and to these two links which helped me :
binding
static resource

Binding ComboBox SelectedItem to Datagrid

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>

EASY way to refresh ListBox in WPF?

I have created a simple form that inserts/updates/deletes a values for Northwind Customers.
Everything works fine, except in order to see a results, I have to close it, and reopen again.
My form looks like this :
I've searched tens of articles on how to refresh ListBox, but all of those use interface implementing, or using DataSets, and stuff I have never heard of and cannot implement. It's a very simple project, using simple procedures. Is there an easy way to refresh the list of customers without adding many lines of code?
The simple answer is: myListBox.Items.Refresh();
Are you using ObservableCollection and does your model implement INotifyPropertyChanged these two things will automaticly update the ListBox on any change. no need to explicitly refresh the list.
Here is a small example of using ObservableCollection and INotifyPropertyChanged, obviously you will populate your ObservableCollection from your SQL database.
Window:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<MyModel> _list = new ObservableCollection<MyModel>();
private MyModel _selectedModel;
public MainWindow()
{
InitializeComponent();
List.Add(new MyModel { Name = "James", CompanyName = "StackOverflow"});
List.Add(new MyModel { Name = "Adam", CompanyName = "StackOverflow" });
List.Add(new MyModel { Name = "Chris", CompanyName = "StackOverflow" });
List.Add(new MyModel { Name = "Steve", CompanyName = "StackOverflow" });
List.Add(new MyModel { Name = "Brent", CompanyName = "StackOverflow" });
}
public ObservableCollection<MyModel> List
{
get { return _list; }
set { _list = value; }
}
public MyModel SelectedModel
{
get { return _selectedModel; }
set { _selectedModel = value; NotifyPropertyChanged("SelectedModel"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml
<Window x:Class="WpfApplication11.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<ListBox ItemsSource="{Binding ElementName=UI, Path=List}" SelectedItem="{Binding ElementName=UI, Path=SelectedModel}" Margin="0,0,200,0" DisplayMemberPath="DisplayMember" SelectedIndex="0" />
<StackPanel HorizontalAlignment="Left" Height="100" Margin="322,10,0,0" VerticalAlignment="Top" Width="185">
<TextBlock Text="Name" />
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding ElementName=UI, Path=SelectedModel.Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Company Name" />
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding ElementName=UI, Path=SelectedModel.CompanyName, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
</Window>
Model
public class MyModel : INotifyPropertyChanged
{
private string _name;
private string _companyName;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
public string CompanyName
{
get { return _companyName; }
set { _companyName = value; NotifyPropertyChanged("CompanyName"); }
}
public string DisplayMember
{
get { return string.Format("{0} ({1})", Name, CompanyName); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
PropertyChanged(this, new PropertyChangedEventArgs("DisplayMember"));
}
}
}
In this case any edit to properties will Update your list instantly, also will update when new Items are added/removed.
How about calling ListBox.UpdateLayout?
Of course you also need to update the particular item(s) so that it returns the updated string from the ToString method.
UPDATE: I think you also need to call ListBox.InvalidateArrange before you call ListBox.UpdateLayout.
Use INotifyPropertyChanged is the best way, refresh the entire list is not a good idea.
Main entrance:
public partial class MainWindow : Window
{
private BindingList<FoodModel> foodList = new BindingList<FoodModel>();
public MainWindow()
{
InitializeComponent();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
foodList.Add(new FoodModel { foodName = "apple1" });
foodList.Add(new FoodModel { foodName = "apple2" });
foodList.Add(new FoodModel { foodName = "apple3" });
FoodListBox.ItemsSource = foodList;
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
foodList[0].foodName = "orange";
}
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
FoodListBox.Items.Refresh();
}
}
Model:
public class FoodModel: INotifyPropertyChanged
{
private string _foodName;
public string foodName
{
get { return _foodName; }
set
{
if (_foodName != value)
{
_foodName = value;
PropertyChanged(this, new PropertyChangedEventArgs("foodName"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
XAML:
<ListBox HorizontalAlignment="Center" Name="FoodListBox" VerticalAlignment="Top" Width="194" Height="150">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding foodName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories