Setting SelectedItem of Listbox or ComboBox through ViewModel MVVM WPF - wpf

Objective: having bound the SelectedItem of a ListBox (or ComboBox) to an instance of an object through xaml, I would like to set the selected instance of the object through the view model and have it reflect on the ListBox or ComboBox.
<ComboBox x:Name="cboServers" HorizontalAlignment="Left" Margin="535,694,0,0" VerticalAlignment="Top" Width="225"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.SelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName">
C# Model View code
public ObservableCollection<AutoSyncServer> Servers { get; set; }
private AutoSyncServer _selectedServer;
public AutoSyncServer SelectedServer
{
get { return _selectedServer;}
set
{
_selectedServer = value;
OnPropertyChanged("SelectedServer");
}
}
The list or combo box populates correctly. Selecting an item on the ListBox or ComboBox will correctly set the SelectedServer object.
However, if I try to write a set statement in C# such as:
Servers.Add(newServer);
SelectedServer = newServer;
The ListBox or ComboBox will correctly add the item and the SelectedServer object will be correctly set on the MVVM model, but the front end will not reflect this selection.
In this specific case, an xml file is read saying what the user had selected last, and when the window opens the ComboBox has nothing selected (the servers are all loaded correctly within it though)
What's missing here?

The actual object in SelectedItem must be an object instance which is found in the Servers collection, in the Object.ReferenceEquals(a, b) sense. Not just the same Name and ID (or whatever) properties; the same exact class instance.
The classic case where people run afoul of this is deserializing equivalent items in multiple places. Servers has a collection of deserialized AutoSyncServer instances, and Settings.SelectedServer is a separately deserialized AutoSyncServer instance, which has identical property values to one of the items in Servers. But it's still a different object, and the ComboBox has no way of knowing that you intend otherwise.
You could override AutoSyncServer.Equals() to return true if the two instances of AutoSyncServer are logically equivalent. I don't like doing that because it changes the semantics of the = operator for that class, which has bitten me before. But it's an option.
Another option is to have one canonical static collection of AutoSyncServer and make sure every class gets its instances from that.
I don't understand why this code didn't work, given the above:
Servers.Add(newServer);
SelectedServer = newServer;
Once newServer is in Servers, it should be selectable. I tested that and it's working for me as you would expect.

i think you must avoid "sub-bindings", they work once when the view ask for, but not well after
Settings.SelectedServer ==> SelectedServer
and if you comment OnServerChanged?.Invoke(this, _selectedServer); what is happening ? it works ?

Related

Databinding causing other functions on page not to work when filling datagrid

I have a datagrid in my View. The datagrid's itemssource property is bound as such
ItemsSource="{Binding}"
Addtionally in the codebehind I have set datacontext by doing the following:
DataContext = ProcedureDatabaseViewModel.Procedures();
The Procedures function in the viewmodel successfully outputs a list which the DataGrid successfully displays.
The issue now is that the datacontext of the entire page is now set to the above. The result of this is that other elements that interect with the VM no longer work. I.E. buttons with commands that are found in the VM. I have tried removing the setting of the datacontext in the code behind but cannot figure out how to populate the datagrid otherwise. Please note that when the DataContext is not set in the code behind the context is changed to the VM, I believe, and thus, the other elements begin working again. I have tried changing the Itemssource property to target the list of objects that I wish to populate the datagrid with but it hasnt worked.
The list is
List<procedure> Procedures
and it is in the ProdureDatabaseViewModel. I tried to target it as
ItemsSource="{Binding ProdureDatabaseViewModel.Procedures}"
But this has not worked either.
Can someone please advise me on the correct way to do this?
The cleanest way is to use ItemsSource to bind your Procedures collection to your DataGrid. For this to work, Procedures has to be a Property. To avoid further issues, use an ObservableCollection. It should look like this:
ObservableCollection<procedure> Procedures { get; set; }
Then you should be able to simply bind it via
ItemsSource="{Binding Procedures}"
I prefer to set the DataContext in the constructor of the view to the complete ViewModel (which I created for this special view).
So I do something like this in the constructor of the view:
public View(ProcedureDatabaseViewModel viewModel)
{
this.DataContext = viewModel;
}
This way everything else should still work and you can use more than just the procedures.
Next you bind the procedures to your datagrid:
ItemsSource="{Binding ProcedureList}"
Do note that for this to work, "Procedures" needs to be a property. It's not clear in your question if it is a function, a property or just a simple class member. If it is a function, you can do it like this in your view model:
public List<procedure> ProcedureList
{
get { return this.Procedures(); }
}
I have tried removing the setting of the datacontext in the code
behind but cannot figure out how to populate the datagrid otherwise.
You can set the DataContext of your DataGrid separately like so, MyDataGrid.DataContext = ProcedureDatabaseViewModel.Procedures();.
And apply separate DataContext for your Page.

Creating a dynamic seach using MvvM and WPF

I am fairly new to mvvm and WPF so I am looking for some pointers to achieve the following.
I would like to create a search section in my app that builds up the search options based on whatever the users selects as their criteria.
So for example the first combo box of the search offers the top level of choices:
Date of Message
Gateway
Direction
Etc..
......
The second section is my operator
So, for example if Date of Message is selected then the user is offered another combo box of choices
Is Between
Last Week
Last Month
Etc..
The last section of the search is based on the operator above, so if Is Between is selected the form displays two Date Pickers from and to. If on the other hand Last Week is selected then nothing is displayed as the search can directly call my SetActionLogsForLastWeek() method once I click my search button.
If the user selects Gateway from the initial list then another combo box is build with a list of choices based on gateways.
I am looking for a tutorial or previous post that points me in the right direction on achieving my goal of building WPF elements based on selection choices from other elements.
Thanks
This is a rather extensive question, and as such there is no definitive answer. I will describe how I would handle this problem. Keep in mind that this might not be the best solution out there.
In order to render the controls which depend on the first combo box use data templates and distinct view models. In the resource section of your main view, or an external resource if you have defined one, write something like this:
<DataTemplate DataType="{x:Type vm:YourViewModel}"> <vw:YourView /> </DataTemplate>
You will have to import the namespaces containing your viewmodels and views (vm: and vw: here). Using content-controls you can render views depending on their viewmodels:
<ContentControl Content="{Binding CurrentViewModel}"></ContentControl>
In your code behind, you will have to have a ViewModel which you can bind to. Depending on the selection of the first combo box, you can now swap out the ViewModel in the code behind in order to render the dependent combo boxes - or nothing at all!
In order to illustrate, let me provide some sample code. Lets start with you main view model. Thats the one containing the collection the first combo box binds to (e.g. the strings "Date of Message","Gateway"...)
public class MainViewModel : BaseViewModel {
public IObservableCollection comboBoxItems;
public BaseViewModel ControlViewModel {get; set;}
private String selectedItem;
public String SelectedItem {
get {
return selectedItem;
}
set {
selectedItem = value;
OnPropertyChanged("SelectedItem");
ChangeControls();
}
}
private void ChangeControls(){
switch(selectedItem):
//swap out the control view model here e.g.
controlViewModel = new GateWayControlViewModel();
}
}
Btw, BaseViewModel is an abstract class which all view models inherit from. It implements INotifyPropertyChanged.
In your main view it is enough to have content control binding to the control view model, as well as the first combo box:
In your resources.xaml you define the data template, as discussed before.
<DataTemplate DataType="{x:Type vw:GatewayControlViewModel}"> <vw:GatewayView /> </DataTemplate>
This will render the GateWayView if the ViewModel, which the ContentControl in the main view is bound to is equal to GateWayControlViewModel. Finally, your GatewayView could look like this:
<ComboBox ItemsSource="{Binding Gateways}"
SelectedValue="{Binding SelectedGateway}" />
Obviously, you will have to make sure your GatewayControlViewModel actually contains the items and properties mentioned in the view. It will also need to derive from BaseViewModel.
I hope this gives you some ideas. Additionally, you might find this link useful (as it contains some very useful tips on how to approach MVVM).
If you want to implement Search in Collections for example in as in Combobox or Grid you should read a bit about ICollectionView. The ICollectionView will allow you to filter collection based on your search criteria. In your example you can also do this by handling selection changed events at the viewmodel and create method to update your data based on selected inputs.

How to get DataGrid.SelectedIndex from another ".cs" file?(MVVM)

Earlier when I wanted to edit a row in a DataGrid then I just set the cursor on the row of a DataGtid and wrote such code in a method of a codebehind form(xxx.xaml.cs):
int k = XXXDataGrid.SelectedIndex;
and then I could retrieve data from a row of a DataGrid.
Now I try to use MVVM pattern of WPF and I have read that all my CRUD operations should pe written in Models. OKAY. I tried to take "DataGrid.SelectedIndex" but it is not possible without creating a handler in codebehind file.
I would like to know how can I take "DataGrid.SelectedIndex" and data of a row of a DataGrid from other classes situated in "Models" of MVVM.
When are value types stored in stack?
I have read a lot of books of C# and always when I read about values and references types then to my mind comes a question: When are value types stored in stack? Cause programmer cannot initializes any value type out from class.
Your ViewModel will have Properties that are populated Model objects. Here you will assign them so the View will be able to display data.
public ParentModel
{
get { return parentModel; }
private set
{
if (parentModel != value)
{
parentModel = value;
RaisePropertyChanged("ParentModel");
}
}
}
public int SelectedItemIndex
{
get { return selectedItemIndex; }
set
{
if (selectedItemIndex != value)
{
selectedItemIndex = value;
RaisePropertyChanged("SelectedItemIndex");
}
}
}
The View will contain the object to display data, be it DataGrid, ComboBox etc. You can use ItemTemplates to customize how the data is displayed, but the key is to bind your data to the control. This can allow data to flow in either the direction mode of only ViewModel to View (OneWay), or View to ViewModel (OneWayToSource), or both (TwoWay). So as the user changes the selected index, the data will flow back to the ViewModel as it is set for TwoWay below. The ItemsSource here is set as OneWay so only the ViewModel can change that for the View.
<DataGrid ItemsSource="{Binding Path=ParentModel.Items, Mode=OneWay}"
SelectedIndex="{Binding Path=SelectedItemIndex, Mode=TwoWay}" />
If ViewModels need to interact with BusinessLogic, just pass the Data. If a seperate ViewModel needs the information, you'll need to use the concept available in your framework, eg. EventAggregator (Prism), to pass data around since they won't have knowledge of each other.
Core concept of MVVM is the binding of models and WPF controls' properties like this. I think you'll want to read up more on it to fully utilize the power and design. Some very helpful questions/answers can be found on StackOverflow, MSDN has several good tutorials, etc.

Looping through (and removing) items of a bound ComboBox

I am new to WPF - and painfully aware of it. I have unsuccessfully searched for answers to this particular problem and am now seeking advice from my more knowledgeable peers!
The scenario
The application on which I am working allows users to either enter new records into a database, or amend existing ones.
I have a form containing a bound ComboBox. It is populated from the database, which is accessed by a WPF service that exposes a DTO.
From the UI perspective, the form has two modes:
1. enter new record
2. amend existing record
The ComboBox in question appears in both cases, but the requirement is to have fewer options visible when the form is in 'amend' mode.
What I am trying to do is loop through the ComboBox items when the form is in 'amend' mode and remove/hide the options that should not appear.
The XAML
<ComboBox x:Name="RecordType" Grid.Column="1" Grid.Row="1" Width="150" HorizontalAlignment="Left" SelectedValue="{Binding Path=RecordTypeID,TargetNullValue=0}"/>
The code behind - and my (feeble!) attempts so far
foreach (ComboBoxItem item in this.RecordType.Items)
{
if (IsApplicable(item.Content.ToString()) == false)
{
item.Visibility = Visibility.Hidden;
}
}
(NOTE: IsApplicable() is a simple method that compares the string it receives to a list of the options that are allowed to appear when the form is in 'amend' mode.)
The problem
As I'm sure many of you will already know ... cannot cast object of type DTO to type System.Windows.Controls.ComboBoxItem
The question(s)
Can I get at the string values in this, or a similar way? If so, how, please?
Correct way to do this would be to apply Filter on Collection View
See Automatically Filtering a ComboBox in WPF
ICollectionView view = CollectionViewSource.GetDefaultView(comboBox.ItemsSource);
view.Filter = IsApplicable
view.Refresh(); // <-- call this whenever you change the view model
It would likely be easier for you if you bound your combobox to an ObservableCollection and then removed the items from the collection when needed.
Here is an example: http://www.tanguay.info/web/index.php?pg=codeExamples&id=304

WPF - Combobox SelectedItem not getting set?

I have a ComboBox that has its ItemsSource bound to a static List<CustomSettings> of options. The ComboBox is part of a form which is bound to a CustomObject class, and one of the properties on that class is a CustomSettingProperty.
I would like to bind the SelectedItem of the ComboBox to the property specified in the CustomObject, however SelectedItem="{Binding Path=CustomSettingProperty}" is not setting the default selected item. Using breakpoints I can see that it is calling the get; method, so I think the problem might be in the fact the CustomSettingProperty is created separately from the List<CustomObject> so WPF does not think it is the same item.
Is there an easy way to do this? Or perhaps an alternative since the CustomSettings class does contain an Id?
If the item that is selected is not the same instance that is contained in the List, you must override Equals() in the CustomObject to let the ComboBox know that it is the same object.
If it's the same instance, maybe it's only a simple thing such as setting the BindingMode to TwoWay:
SelectedItem="{Binding Path=CustomSettingProperty,Mode=TwoWay}"
I found the solution, It was The Prism's Event Aggregator was passed with reference type so That the ui thread stops processing

Resources