WPF Combobox behavior weird - wpf

I have 2 comboboxes which we will call cbo1 and cbo2. Now, there is a relationship between cbo1 and cbo2. when i select an item at cbo1, the cbo2 ItemsSource is updated (since it is bound to the SelectedItem) anyway, below is the sample XAML code for it.
<ComboBox x:Name="cbo1" Grid.Row="0" Grid.Column="1" Margin="5" SelectedItem="{Binding Path=Brand}"></ComboBox>
<ComboBox x:Name="cbo2" Grid.Row="1" Grid.Column="1" Margin="5" SelectedItem="{Binding Path=Model}" ItemsSource="{Binding ElementName=cbo1, Path=SelectedItem.Models}" DisplayMemberPath="Name"></ComboBox>
the objects used are Brand and Model. Brand has a property named Models which contain a collection of Model objects ( typeof IList ). So basically, a one-many relationship between the 2 classes.
By the way, those 2 classes are used in NHibernate. Now, when I run the app, cbo1 which contains a collection of Brand objects is loaded with the items first. When I select a Brand item, the cbo2 with the Model collection is populated. As you have noticed, both Comboboxes have SelectedItem property bound to the current object properties Brand and Model specifically. When I select a Model on the cbo2, it does not reflect to the current object's Model property. Anything i missed?

typo: the first combo is called cbo1, but the second combo is binding to cbxBrand; but since you say the Models do appear, I'm guessing this is OK in your actual source code, and you renamed it for the Question here?
Anyway, your code completely worked for me, I put a breakpoint on the setter of the Model property and it hit it no probs, so the only thing I can possibly guess at is the Window's DataContext maybe incorrect?
Can you post your code-behind (or ViewModel) ?

Related

Where should the crud logic be implemented in mvvm?

In my MVVM Light application I do a search in a customer list. The search narrows the customer list which are displayed in a master/detail view with a datagrid (the master CustomerSearchResultView) and a separately defined usercontrol with FirstName, Lastname, Address etc, etc (the detail - CustomerSearchDetailView). Here are the main content of the master/detail view:
<StackPanel MinWidth="150" >
<TextBlock Text="Customer Search Result List" />
<Grid>
<DataGrid Name="CustomerList" ItemsSource="{Binding SearchResult}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
.....
</DataGrid>
</Grid>
<Grid Grid.Column="2">
<TextBlock Text="Customer Details" Style="{StaticResource Heading2}" Margin="30,-23,0,0"/>
<content:CustomerSearchDetail DataContext="{Binding SelectedRow}" />
</Grid>
</Grid>
</StackPanel>
Both have their corresponding ViewModels. Please remark the DC for the CustomerSearchDetail, SelectedRow - it is a property on the CustomerSearchResultViewModel and is defined like this:
private Customer _selectedRow;
...
public Customer SelectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
RaisePropertyChanged("SelectedRow");
}
}
...
Because of this I have not defined any DC on the CustomerSearchDetailView - it is set in the Binding on the "Master" view (as shown above) and it seems to work ok.
In my Model folder I have created the Customer class that is in use here. It implements ObservableObject and IDataErrorInfo and have public properties that raisepropertychanged events.
I run the application and everything seems to be ok. Note: the ViewModel for the CustomerSearchDetailView (that is CustomerSearchDetailViewModel.cs) is at this stage just an empty shell and not in use (as far as I can see ... the constructor is never accessed)
Now I want to add Save/Update functionality to my customer in the detail view. Ok, I add a Save button to the CustomerSearchDetailView like this:
<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I create my "SaveCommand" RelayCommand property in my CustomerSearchDetailViewModel - but it is never accessed.
Hmmmmm ... well after some googling back and forth I come up with this:
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I defined the "MyCustDetails" as a resource in this view pointing to the CustomerSearchDetailViewModel. And voila! I now hit the method when debugging ... but alas, my customer was of course "null". (In fact I spent 2 hours implementing the CommandParameter here and binding it to the "SelectedRow" Property on the master view - but the customer was still "null").
More googling and searching for mvvm examples, and I implemented my "SaveCommand" on the Customer class (the model object). And guess what? The edited customer got passed along - I could send it to my EF layer and everything seems to be ok ....
And - If you are still with me - here comes my questions:
1.) I would like - and thought that was the "proper MVVM way" of doing things - to have my CRUD/Repository accessing in the ViewModel. How can I do that in my scenario?
2.) Now that I have my CRUD in place via the Model class (Customer) - should i bother with question 1? In fact I have deleted the CustomerSearchDetailViewModel and everything runs ok. I feel I have invented the View - Model (MV) framework ... :-P
I would very much like feedback on this - and I apologize for this "wall of text".
Assuming DC means DataContext
Just my opinion:
First question is are you doing anything special with SelectedRow in CustomerSearchResultViewModel?
If the answer is no, just get rid of that property and have your CustomSearchDetailView bind directly to the DataGrid using {Binding ElementName=CustomerList, Path=SelectedItem}
Now your Save / update Commands need to be used by Button's in CustomerSearchDetailView. So instantly I'd be inclined to using a separate VM for that View and have these Command's defined there.
Now you mentioned these Commands were not accessed. Well the answer for that is because in your program you're never actually creating the CustomerSearchDetailViewModel.
Normal operation is your View's DataContext is it's VM(If it requires one. In your case you do imo cos you need it to hold your Commands)
looking at your code I'd guess your using MVVM Light. So in ViewModelLocator you have your Main property and in your Main View, you got the DataContext set using that Main property and Source={StaticResource Locator} where Locator is the ViewModelLocator created in App.xaml Resources. This thereby creates that ViewModel for that view defining that DataContext. You can ofcourse do the same in code-behind but let's not go off topic.
So in your case you got the DataContext set as SelectedRow which is of type Customer and Binding's are resolved using DataContext and that's why when your command's are defined in Customer it works fine but when it's in the VM it did not.
So why did it work when you had the commands in your VM and used
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
^^ That worked because the DataContext was not used since Source has been specified explicitly. and where-ever MyCustDetails was defined in resources, there the VM got created.
So it worked what's wrong with that?
Well it's quite a big mess. Also just like you mentioned Customer details in that VM was null. Well I hope you can guess why that was by now. It's because your VM was created in resources via x:Key="MyCustDetails" but nothing in it was ever used or set apart from when the Binding's referred to it explicitly
In this system we got commands that refer either to the Model which is plain wrong or the VM which is created as a resource just for this purpose. The DataContext is heavily linked to the "SearchResults" view making it not so easy for future extensions or layout updates.
If we keep the View <-> VM a 1 <-> 1 relattion we can avoid all this confusion. So in summary we can answer both your question's together. While this works, please don't let your code be like this and tweak it to better help expansion for future and comply with some basic guidelines.
So how do we do that?
Approach 1:
In your CustomerSearchDetail View, add a DependencyProperty of type Customer lets call this say SelectedCustomer.
Now replace DataContext="{Binding SelectedRow}" with SelectedCustomer="{Binding SelectedRow}" in CustomerSearchResultView
Now set the DataContext of your CustomerSerachDetailView as it's VM similar to how CustomerSerachResultsView links to it's VM(guessing through DataContext Binding in xaml using the ViewModelLocator)
Now you can have your commands in Button's of CustomerSerachDetailView just as <Button Command="{Binding SaveCommand}" ...
Finally because SelectedRow is no longer the DataContext of the CustomerSerachDetailsView, your Bindings for FirstName, Lastname, Address will all appear to stop working.
We got plenty of options to address this.
First is to in each Binding use a RelativeSource FindAncestor binding pointing to CustomerSerachDetailsView and there via the CurrentCustomer DP(DependencyProperty) we created before get the appropriate field.
eg:
<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />
now if you have multiple properties this is gonna soon start getting annoying to type. So then pick a common ancestor(say 3 of these TextBlocks are grouped under a StackPanel) and apply it's DataContext as the CurrentCustomer element via a similar binding to ^^. Now the StackPanel's children DataContext will be the Customer element so in each of their binding's you don't have to do the whole RelativeSource thing and can just mention {Binding Path=FirstName} and so on.
That's it. Now you got two view's with their own respective VM and a Model(Customer) and each have their respective tasks.
Great, we done? err not quite yet.
While Approach 1 is better than what we started with it's still just "meh". We could do better.
Approach 2
MVVMLight has a Messenger class that will allow you to communicate between different classes in a weak dependent format. You need to look into this if you haven't already.
So what do we do with Messenger?
pretty simple:
In the setter of SelectedRow in CustomerSearchResultsViewModel we'll send a message with the new incoming value to CustomerSearchDetailsViewModel.
Now in CustomerSearchResultsViewModel we'll add a property CurrentCustomer and assign it this incoming value.
In the CustomerSerachDetailsView we no longer create a DP. Which means we no longer set SelectedRow to anything(DataContext or DP) in the CustomerSerachDetailsView from CustomerSearchResultsView ( sweet less work :) )
As for the way we assign DataContext of CustomerSerachDetailsView or way we bind the Button.Command - They remain same as Approach 1
Finally the actual "FirstName" and so Binding's. Well now CurrentCustomer is a property of the CustomerSearchDetailsViewModel. So binding to it just like how the Button bind's to it's commands
^^ this works fine now cos DataContext for the TextBlock is the VM and the property CurrentCustomer exists in it.

How to update an object's inner property from UI to VM? (WPF MVVM)

I have a property in my ViewModel, I'll call it "Project" which contains several nested lists inside of it. None of such lists has an associated property in the view model since I can show everything in xaml by using triggers and bindings.
My xaml shows the Project hierarchy in a treeview and its details in several views (a content control selects the right view depending on which item is selected on the treeview). One of those "details" is a property for the objects contained in one of the nested lists, I'm showing it in a textbox so the user can edit it, the problem I'm having is I don't see that property updated in the Project property in the VM once I make changes to it in the textbox.
I was told I have to create a property in the VM for that specific object's property I'm trying to edit, I just don't know how since the object is deep inside one of the nested lists of my Project object.
Common mistakes are:
Using OneWay binding mode instead of TwoWay
In XAML:
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" />
Not realizing that the update is not propagated to the VM until after the TextBox lost focus.
this is the default for the TextBox:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=LostFocus}" />
You could change it to:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />

Compare objects to set selected value in pre-loaded combobox silverlight MVVM

I'm trying to set seletected value to a pre-loaded combobox using silverlight with MVVM.
I load these combobox items before selecting value.
For example I have a combobox to select a country. My first step is to load a List which is bound to the combobox source. This is loading perfectly.
After this, I have a "SelectedCountry" object bound to the selectedItem of the combobox in a two-way binding.
This is working perfect when I select any of the combobox values and my SelectedCountry object is correctly selected.
The problem comes when I try to assign the selected value in my ViewModel. This way, the combobox selecteditem is not updated.
I suppose this is because, on fact, they are not the same object (they have the same values but they are diferent references).
Should this work if I re-implement the equals method? Or should I find the same object by searching into the List?? This would be very easy because this two countries would be the same if they have the same id... but I can have more complex objects and I think the equals method would be better.
Thanks in advance!!
Edit for adding some code example:
<ComboBox Grid.Column="7" Margin="6,0" Name="cBTipoPoliza" VerticalAlignment="Center" TabIndex="4" ItemsSource="{Binding TiposPolizas, Mode=OneWay}" SelectedItem="{Binding TipoPoliza, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding nombre_tipo}" />
</DataTemplate>
</ComboBox.ItemTemplate>
Usually I just override the .Equals() method to check if they are equal by ID or Name
You should try to avoid having multiple copies of the same object in memory at the same time. One method to do this is to have your VM be the source of objects. When you load the list have the VM do it and expose an AvailableCountries ObservableCollection property on the VM that your ComboBox can bind to.
If your objects are semantically equal based on ID, definitely override Equals and == and != and hashcode. However be careful because if you're enabling people to update the objects you can run into collisions (even within the same instance of the app) where one screen is holding onto stale data.

How to pass data between controls and persist the values in WPF

I am stuck on how to pass data from one control to another. If I have a listbox control and the Contol Item contains a datatemplate which renders out 5 fields ( first name, last name, email, phone and DOB) all of which come from an observable collection. How can I allow the user to select a listbox item and have the valuesbe stored within a new listbox control?
Is this done through the creation of a new collection or is there a more simple way to bind these values to a new control?
thank you,
If it is not too late, I would strongly recommend that you use the MVVM pattern. The problem you are facing is typical for WPF without a decent presentation model and wont be the last one.
Using MVVM you would pass data between controls/views through the ViewModel. In your example you would have a PersonViewModel with an ObservableCollection containing first name, last name, email and DOB. Additionally it would have a property SelectedItem. This property can be bound to in a lot of different controls/views without them having to know each other.
Let's say you have a:
<ListBox Name="DemoList" ItemsSource="{Binding ...}">
<ListBox.ItemTemplate>
...
</ListBox.ItemTemplate>
</ListBox>
And another control, maybe a TextBox:
<TextBox Text="I want to bind this to the Email property" />
You can achieve this pretty easily, with:
<TextBox Text="{Binding ElementName=DemoList, Path=SelectedItem.Email}" />
Note the ElementName property of the Binding. This allows you to bind relative to another control, and in this case you want the SelectedItem of your ListBox. SelectedItem will contain an element of the collection in the ItemsSource (or null if nothing is selected), so you can then bind to its properties.
It gets more complex if you want to support multiple selection, but it doesn't sound like this is a requirement for you.

WPF CheckBox IsChecked can't be reset when page(data source) is updated

I have question on the checkbox.
First of all,
I have a usercontrol which has a list box like this and this user control will be switched by 2 button and then the data source is changed and then the the displayed officer status will be changed:
When I check the checkbox, Officers[0].IsOnDuty will be changed to true.
The problem is:
When I click another button and switch to another data source, this checked check box is still checked but the Officers[0].IsOnDuty for this data source is false.
How to solve this?
The data context of the list box item is an item for your officers collection, not the collection itself. And using a one way binding is incorrect, as the data source (the officer) will not be updated. So change the DataTemplate to:
<CheckBox IsChecked="{Binding Path=IsOnDuty, Mode=TwoWay}" />
*Here is the list box xaml:
<ListBox ItemsSource="{Binding OfficersCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Officers[0].IsOnDuty, Mode=OneWay}" />
*
The problem with your approach is that once you change the ItemsSource (by switching to the next page) your chekcbox is still bound to the item of the first collection. I think this happens because you explicitly use an indexer for the binding Path=Officers[0].IsOnDuty
Your samplelist box xaml does not really make sense. the ItemsSoruce is a OfficerCollection and your ItemTemplate binds to a collection of Officers too. Depending on what you are trying to accomplish you should do one of the following:
If your are just interested in the first officer (as your sample suggest), add a DependencyProperty FirstOfficer (or a INotifyPropertyChanged) property to your collection and bind to it: IsChecked="{Binding Path=Officers.FirstOfficer, Mode=OneWay}"
If you however are interested in all Officers and want checkboxes for all of them you should create a DataTemplate for the Officer type and use this as the ItemTemplate.
Generally you can stay out of a lot of trouble if you stick with MVVM and really tailor your ViewModel objects very close to what the View needs so you can bind your View to the ViewModel in the simplest possible way. Think of the ViewModel as the View you want to build but without a visual representation.

Resources