I'm trying to display the data from two sql ce 3.5 sp1 database tables linked with foreign key - Customers and Orders. When the customer is selected in a datadrig, I want the other grid to be populated with the Orders.
I'm using a query:
var profiles = from c in db.Customers.Include("Orders")
select c;
And in my ViewModel:
private Models.Customers _selecteditem;
public Models.Customers SelectedItem
{
get { return _selecteditem; }
}
the view looks like this:
<Grid>
<toolkit:DataGrid x:Name="dg1" ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedItem, mode=TwoWay}">
</toolkit:DataGrid>
<toolkit:DataGrid x:Name="dg2" ItemsSource="{Binding Path=SelectedItem.Orders}">
</toolkit:DataGrid>
</Grid>
The error I'm getting is:
Warning 1 Field 'Clients.ViewModels.CustomerViewModel._selecteditem' is never assigned to, and will always have its default value null
How to make it work correctly? When I just want to display Customers it is ok.
Thanks for any suggestions.
You need a setter for SelectedItem
private Models.Customers _selecteditem;
public Models.Customers SelectedItem
{
get { return _selecteditem; }
set { _selectedItem = value; }
}
Also, since you are using it in a binding you'll want the ViewModel to implement INotifyPropertyChanged so it'll actually be:
private Models.Customers _selecteditem;
public Models.Customers SelectedItem
{
get { return _selecteditem; }
set
{
if (_selectedItem != value)
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
If Martin's answer doesn't help, have a look at the DataGrid.SelectionUnit and make sure it is set to "FullRow" not to "CellOrRowHeader" like I had it.
If you have it set to "CellOrRowHeader", the first click on a cell will set the SelectedItem to null.
I thought I'd add this in case someone else had the same annoying issue.
Related
I am using MVVM in a WPF application with C# and got a problem binding a ComboBox correctly.
This is my ComboBox line in the XAML:
<ComboBox ItemsSource="{Binding Repository.Models}" SelectedValue="{Binding Repository.SelectedModel}" DisplayMemberPath="Name"></ComboBox>
This is the interesting part of my Repository:
class Repository : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Model> _models;
public ObservableCollection<Model> Models
{
get
{
return _models;
}
set
{
_models = value;
NotifyPropertyChanged("Models");
}
}
private Model _selectedModel;
public Model SelectedModel
{
get
{
return _selectedModel;
}
set
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
This is the interesting part of my Model class:
abstract class Model : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
So when I select/change different items of the combobox a DataGrid that is binded to Repository.SelectedModel.Parameters does update just as i want it to.
Because of that I know, that the binding does work!
When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.
So the binding itself does work, but the binding to the ComboBoxLabel somehow fails.
I tried a lot of things like switching between SelectedItem and SelectedValue, between Binding and Binding Path, between IsSynchronizedWithCurrentItem true and false, but nothing worked so far.
Do you see my mistake?
Thanks in advance!
EDIT
Here is the interesting part of my MainWindowViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private Repository _repository;
public Repository Repository
{
get
{
return _repository;
}
set
{
_repository = value;
NotifyPropertyChanged("Repository");
}
}
And here is my App.xaml.cs where I init my DataContext:
//init point of app
public partial class App : Application
{
private MainWindowViewModel mainWindowViewModel;
//gets fired as the app starts
protected override void OnStartup(StartupEventArgs e)
{
//create the ViewModel
mainWindowViewModel = new MainWindowViewModel();
//create the mainWindow
var mainWindow = new MainWindow();
mainWindow.DataContext = mainWindowViewModel;
//show the mainWindow
mainWindow.Show();
}
When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.
Looks like the deserialization is the problem.
You have a selected item which was deserialized. That means that a new Model instance was created which has a Name of whatever, and Properties that are whatever. And you have a list of Model instances in an ObservableCollection<Model> which are displayed in a ComboBox.
And you assure us that at least sometimes, you have ComboBox.SelectedItem bound to SelectedModel, though for some reason the code in your question binds ComboBox.SelectedValue instead. That's not going to work. Here's how ComboBox.SelectedValue would be used:
<ComboBox
ItemsSource="{Binding Repository.Models}"
SelectedValuePath="Name"
SelectedValue="{Binding SelectedModelName}"
/>
...and you would have to have a String SelectedModelName { get; set; } property on your viewmodel. The Name property of the selected Model would be assigned to that by the ComboBox when the selection changed. But you don't have SelectedModelName, and you don't want it, so forget about SelectedValue.
Back to SelectedItem. The ComboBox gets the value of SelectedModel from the binding, and tries to find that exact object in its list of Items. Since that exact object is not in that list, it selects nothing. There is probably an item in Repository.Models that has the same name and has identical Properties, but it is not the same actual instance of the Model class. ComboBox doesn't look for an identical twin of the value in SelectedItem; it looks for the same object.
SelectedModel.Properties works in the DataGrid because the DataGrid doesn't know or care what's in Models. You give it a collection, it's good.
So: If you want to deserialize a SelectedModel and have it mean anything, what you need to do is go ahead and deserialize, but then find the equivalent item in Repository.Models (same Name, same Properties), and assign that actual object instance to SelectedModel.
You may be tempted to overload Model.Equals(). Don't. I've done that to solve the same problem. The resulting behavior is not expected in C# and will bite you, hard, and when you least expect it, because you are invisibly altering behavior that happens in framework code. I've spent days tracking down bugs I created that way, and I'll never do it to myself again.
Try SelectedItem instead of SelectedValue in ComboBox.
I have scenario in Combobox, We want to change the current SelectedItem of Combobox when certain value is selected.
For Instance: We have designation Combobox having values: CEO, Manager, Dev, QA..
When CEO is selected we would like to change it to Manager value.
SelectedValue is bound to property in ViewModel.
Aha! I thought it was a silly question and could be answered without spending much effort :P. But, it thought me a lesson that WPF is not working the way what I think as always.
Here is the sample working solution.
MainWindow.xaml
<Grid>
<ComboBox Width="100" Height="50" ItemsSource="{Binding ComboList}" SelectedValue="{Binding Selected, Mode=TwoWay, IsAsync=True}"/>
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
ObservableCollection<string> lst = new ObservableCollection<string>();
lst.Add("CEO");
lst.Add("Tester");
lst.Add("president");
lst.Add("Developer");
lst.Add("Manager");
MainWindowViewModel vm = new MainWindowViewModel() { ComboList = lst, Selected = "Employee" };
this.DataContext = vm;
}
MainWindowViewModel.cs
public class MainWindowViewModel : INotifiPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> ComboList { get; set; }
private string selected;
public string Selected
{
get { return selected; }
set
{
selected = value == "CEO" ? "Manager" : value;
OnPropertyChanged("Selected");
}
}
}
The Key is IsAsync=True which did the trick here. Thanks to Martin Harris for his answer
I have a ListView with items, that contain string field Name among others. Items in the ListView are sorted by this field:
SortDescription descr = new SortDescription("Name", ListSortDirection.Ascending);
list.Items.SortDescriptions.Add(descr);
I also have a TextBlock in which I want to display the Name of the first item in the sorted ListView. Items can be added, removed and edited at runtime, so I would like to use some kind of binding like the following one (doesn't work, just for example):
<TextBlock Text="{Binding ElementName=list, Path=Items[0].Name}"/>
1) How can I achieve the desired behavior using a binding?
2) If such binding can't be created, which is the most convenient way to succeed?
Any thoughts and hints would be appreciated.
UPDATE
Content of the main window:
<StackPanel>
<TextBlock Name="nameFirst" Text="{Binding ElementName=list, Path=Items[0].Name}"/>
<ListView Name="list" ItemsSource="{Binding ElementName=mainWnd, Path=List}" DisplayMemberPath="Name" Loaded="list_Loaded"/>
</StackPanel>
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Item(string name)
{
Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public ObservableCollection<Item> _list;
public ObservableCollection<Item> List
{
get { return _list; }
set
{
_list = value;
OnPropertyChanged("List");
}
}
public MainWindow()
{
InitializeComponent();
List = new ObservableCollection<Item>();
List.Add(new Item("1"));
List.Add(new Item("2"));
List.Add(new Item("3"));
}
private void list_Loaded(object sender, RoutedEventArgs e)
{
SortDescription descr = new SortDescription("Name", ListSortDirection.Descending);
list.Items.SortDescriptions.Add(descr);
}
}
When the application is started, items in the ListView are sorted in the descending order: "3", "2", "1", but nameFirst TextBox still displays "1", though now it should display "3".
Actually, what you wrote should do the work. The problem is probably not in the xaml.
You said the items contains a field Name. WPF can only bind to public properties make sure to use them, and not public members. It's also possible you're seeing old values if you changed them after the window was opened. Make sure your items implement the INotifyPropertyChanged interface, and the properties invoke OnPropertyChanged.
If you're still having problems, please post more of your code, specifically your data items class and the place where you set the SortDescriptor.
Lastly, though it's not what you asked about. You can bind to the current item in an array by using the / operator. To use your code:
<TextBlock Text="{Binding ElementName=list, Path=Items/Name}"/>
You control the "current" item using CollectionViews and the IsSynchronizedWithCurrentItem property.
Ok so I have spent hours now trying to figure this out and I cant.
I have the below combo box which is binding correctly to my collection of data.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValuePath="AnswerId"
SelectedItem="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer"/>
The Selected Item however is not populating top my Selected Answer property. I put a textbox on the form and bound it to SelectedAnswer.Answer and that is binding to the answer correctly.
For some reason though my combo box will not bind the selected answer
I have read something about the layout of the combo box property and tried changing that, also stepped through the getter and setter of the property to ensure it is not clearing down (which is not as it will bind to the text box)
Please help with this.
SurveyAnswer:
public class SurveyAnswer : INotifyPropertyChanged
{
private Guid answerId;
public Guid AnswerId
{
get { return answerId; }
set {
answerId = value;
NotifyPropertyChanged("AnswerId");
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
answer = value;
NotifyPropertyChanged("Answer");
}
}
public Guid SurveyLineID { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set {
isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#region NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
I think you need to change SelectedItem to SelectedValue. Sometimes that order of parameters matters as well.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValue="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer" SelectedValuePath="AnswerId"/>
This is helpful:
http://johnpapa.net/binding-to-silverlight-combobox-and-using-selectedvalue-selectedvaluepath-and-displaymemberpath
I've got a datagrid bound to a datatable, with a ComboBoxColumn. The XAML for this column is as follows:
<DataGridComboBoxColumn Header="Rep Name" SortMemberPath="RepName"
ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged, Source={StaticResource EmployeeList}, Path=Employees}"
SelectedValueBinding="{Binding Mode=TwoWay, Path=EmpId}"
SelectedValuePath="EmpId" DisplayMemberPath="RepName" />
My Employees class:
public class EmployeeList : INotifyPropertyChanged
{
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>();
public EmployeeList()
{
...
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set { _employees = value; NotifyPropertyChanged("Employees"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Employee : INotifyPropertyChanged
{
private int _id;
public int EmpId
{
get { return _id; }
set { _id = value; OnPropertyChanged("EmpId"); }
}
public string RepName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
The DataTable serving as items source for the grid contains an "EmpId" column and a "RepName" column. The combobox is populated with all my employees, and when I make a selection, it is reflected in the datatable. However, when the screen loads, the currently assigned employee is not selected by default in the combobox. I thought that the SelectedValueBinding property of the combobox would handle this... what am I doing wrong?
Update for clarification:
The datagrid is bound to a datatable which includes an EmployeeID column. Let's assume that when the screen loads, there are three rows in the table with EmployeeIDs 1, 2, and 3. I need the combobox column in the first row to have EmployeeID 1 selected, the second row to have EmployeeID 2 selected, and the third row to have EmployeeID 3 selected.
If I understand your problem correctly, the ComboBox doesn't select the first item in the collection by default?
If that's the case, you should set the property IsSychronizedWithCurrentItem on the ComboBox to true. Info about Selector.IsSynchronizedWithCurrentItem here.
...
Well, unfortunately upon further research, DataGridComboBoxColumn does not have a IsSynchronizedWithCurrentItem property. :/ But, you can create a DataGridTemplateColunm that looks something like this:
<DataGridTemplateColumn Header="Rep Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Employees}"
IsSynchronizedWithCurrentItem="true" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColunm>
Hope that helps!