SelectedItem not shown in ComboBox - wpf

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.

Related

WPF - Binding Textbox Text to a class property

I am doing some changes in my WPF project to make it less deprecated.
One of the things I am trying to do is Binding my Textbox.Text value to a simple Class as shown below.
<TextBox x:Name="txtNCM"
Grid.Column="1"
Margin="5"
MaxLength="8"
Text="{Binding Path=Name}"
</TextBox>
public partial class wCad_NCM : UserControl, INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public wCad_NCM()
{
InitializeComponent();
}
}
Everytime I use the Immediate Window to display the Name's value, it is shown as null. I am really new to this, so I had to search for a similar situation to adapt, but I don't know how to make this work :(
You need to set the DataContext and give Name a value.
To do that, change your constructor to include this:
public wCad_NCM()
{
InitializeComponent();
DataContext = this; // Sets the DataContext
Name = "Test";
}
This should make it work, but is typically bad practice. See http://blog.scottlogic.com/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html for more details.
Additionally, I tried running this and ran into a name hiding problem. Try using a variable name other than Name as FrameworkElement already contains it.

WPF: bind to the first item in the sorted ListView

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.

Alternative to groupbox?

I'm diving into WPF, coming from a Winforms background where I used groupboxes to show and hide "panels" depending on what menu options were clicked. This was a bit of a nightmare at designtime having multiple overlapping groupboxes.
Does WPF solve this problem? Is the groupbox still the way to go? Or are there better solutions?
The WPF GroupBox has a property on it called 'Visibility' (inherited from UIElement) which can be controlled in a View Model via binding.
This Xaml fragment shows a GroupBox's visibility being bound to a property called 'MyGroupBoxVisibility'...
<Grid>
<GroupBox Header="This and that" Visibility="{Binding MyGroupBoxVisibility}" Background="Plum"/>
</Grid>
A sample View Model which contains the 'MyGroupBoxVisibility' is...
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
HideTheGroupBox();
}
private void ShowTheGroupBox()
{
MyGroupBoxVisibility = Visibility.Visible;
}
private void HideTheGroupBox()
{
MyGroupBoxVisibility = Visibility.Collapsed;
}
private Visibility _myGroupBoxVisibility;
public Visibility MyGroupBoxVisibility
{
[DebuggerStepThrough]
get { return _myGroupBoxVisibility; }
[DebuggerStepThrough]
set
{
if (value != _myGroupBoxVisibility)
{
_myGroupBoxVisibility = value;
OnPropertyChanged("MyGroupBoxVisibility");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
This sample has two methods in it which toggle the visibility back and forth. You can use this technique to transfer your WinForms stuff into WPF and also keep it within MVVM.
ETA: Note that it's vital to understand the difference between Collapsed and Hidden visibilities. Please see http://msdn.microsoft.com/en-us/library/system.windows.visibility(v=vs.110).aspx for an explanation

Bind observerbaleCollection to wpf combo box

I'm having Class call Apps. It has Observable collection called AvailableTypes. I want to bind this AvailableTypes observable collection to the wpf ComboBox. When form is loaded these AppId should loaded into comboBox.. Would you give me a solution to this one?
class Apps: INotifyPropertyChanged{
ServiceReference1.AssetManagerServiceClient client;
ObservableCollection<string> availableType;
public ObservableCollection<string> AvailableTypes
{
get
{
if (availableType == null)
{
availableType = new ObservableCollection<string>();
}
client = new ServiceReference1.AssetManagerServiceClient();
List<string> AssestList = client.GetAppIds().ToList<string>();
foreach (string appid in AssestList)
{
availableType.Add(appid);
}
return availableType;
}
set
{
availableType = value;
NotifyPropertyChanged("AvailableTypes");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In your xaml code, this is a simple example of how you can bind to your combobox.
<ComboBox ItemsSource={Binding Path=AvailableTypes} />
You will also need to load your viewmodel into the DataContext of your window too.
var window = new MainWindow
{
DataContext = new Apps()
};
window.Show();
If you want open the window on App startup, you can do this instead
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow
{
DataContext = new Apps()
};
window.Show();
}
}
Don't overload property getters/setters. Make it simplier.
I recommend to use auto properties and NotifyPropertyWeaver or use PostSharp post build compile-time injected instuctions to support INotifyPropertyChanged interface.
This makes your view model more readable and easy to manage/understand.
In your form 'Loaded' event or 'NavigatedTo' in SL you can start loading your data from anywhere you want and set corresponding properties after loading completed (in callbacks/events handlers, don't forget about using UI dispatcher while updating binded properties)

How can I refresh a combobox after modifying its ItemsSource ObservableCollection

The problems is simple: when ItemsSource is updated Combobox doesn't "refresh" e.g. new items don't appear to be added to the list of items in the combobox.
I've tried the solution from aceepted answer to this question: WPF - Auto refresh combobox content with no luck.
here's my code,
XAML:
<ComboBox Name="LeadTypeComboBox" ItemsSource="{Binding LeadTypeCollection}" />
ViewModel:
public ObservableCollection<XmlNode> LeadTypeCollection { get; set; }
the way I update this collection is in the separate method, which loads data from updated XML file: this.LeadTypeCollection = GetLeadTypesDataSource();
I've also tried using Add for testing purposes:
this.LeadTypeCollection = GetLeadTypesDataSource();
ItemToAdd = LeadTypeCollection[LeadTypeCollection.Count - 1];
this.LeadTypeCollection.Add(ItemToAdd);
the code updating collection definitely kicks off, I can see new items in this collection when debugging, but I don't see them in the combobox.
Doing this in the xaml code-behind works: LeadTypeComboBox.ItemsSource = MyViewModel.GetLeadTypesDataSource(); but I'd like to achieve this with MVVM, i.e. the code must be in ViewModel which isn't aware of LeadTypeComboBox control.
Firedragons answer would work, but i would prefer to initialize the LeadTypeCollection just once and use clear, add remove to update your collection.
var update = GetLeadTypesDataSource();
this.LeadTypeCollection.Clear();
foreach(var item in update)
{
this.LeadTypeCollection.Add(item);
}
your xaml binding should work if the datacontext is right
<ComboBox Name="LeadTypeComboBox" ItemsSource="{Binding LeadTypeCollection}" />
I think I have seen this before and the solution was to update the collection property to raise the change.
i.e.
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<XmlNode> leadTypeCollection;
public string LeadTypeCollection
{
get { return leadTypeCollection; }
set
{
if (value != leadTypeCollection)
{
leadTypeCollection = value;
NotifyPropertyChanged("LeadTypeCollection");
}
}
public MyViewModel()
{
leadTypeCollection = new ObservableCollection<XmlNode>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
PropertyChanged.Raise(this, info);
}
}
I have an extension method for raising the property (as found elsewhere on stackoverflow):
public static void Raise(this PropertyChangedEventHandler handler, object sender, string propertyName)
{
if (null != handler)
{
handler(sender, new PropertyChangedEventArgs(propertyName));
}
}
A simple method is to change ItemsSource with empty list and then change it back to your updated source. A snippet from my project which is working:
RulesTable.ItemsSource = Rules.rulesEmpty;
RulesTable.ItemsSource = Rules.Get();

Resources