WPF MVVM and parent-child combobox - wpf

I'm sure I already found this on StackOverflow, but I don't seem to be smart enough to find it again
What I want to do (in WPF using MVVM) is this:
cmbSelectedAddressRegion: populated with the list of region
cmbSelectedAddressCities: populated with the list of cities in that region
When the user click on a region in cmbSelectedAddressRegion the items in cmbSelectedAddressCities should be the cities of that region only
I have an XAML like this
<ComboBox Name="cmbSelectedAddressRegion"
SelectedValue="{Binding Path=selectedAddressItemRegion, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=selectedAddressIsEnabled}"
Style="{StaticResource style_flat_ComboBox}"></ComboBox>
<ComboBox Name="cmbSelectedAddressCities"
SelectedValue="{Binding Path=selectedAddressIdCities, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="id"
SelectedValuePath="id"
ItemsSource="{Binding ElementName=cmbSelectedAddressRegion, Path=SelectedItem.Cities}" IsEnabled="{Binding Path=selectedAddressIsEnabled}"
Style="{StaticResource style_flat_ComboBox}"></ComboBox>
When I click on a region in cmbSelectedAddressRegion the cmbSelectedAddressCities is correctly populated
I also have a VM vmCustomer with a lot of DependencyProperties (amongst them selectedAddressItemRegion and selectedAddressIdCities)
When I select the customer from the master list (another combobox in the window which holds the list of customers) I see the cmbSelectedAddressRegion correctly showing the Region, but I don't see anything in the cmbSelectedAddressCities. Again if I click on the cmbSelectedAddressRegion the cmbSelectedAddressCities is populated and the currently selected cities (in the vmCustomer) is selected
The cmbSelectedAddressRegion.itemssource is bounded (in bode behind file) to an ObservableCollection(of vmAddressRegion)
Each vmAddressRegion has, amongst other DependencyProperties, a cities properties which returns an ObservableCollection(of vmAddressCities)
The ObservableCollection(of vmAddressRegion) is populated when the window is created. At the same time, for every item of ObservableCollection(of vmAddressRegion) (of type vmAddressRegion) the ObservableCollection(of vmAddressCities) is populated with the corresponding items)
I hope I've been clear enough
Any suggestion how to resolve the problem above (the cmbSelectedAddressCities not being "populated")?
Thanks for any help

The WPF ComboBox needs to be handled with care. Bindings easily get confused if ItemsSource and SelectedValue/SelectedItem change in the "wrong" order, especially if you use SelectedValue.
My advice would be to replace the "SelectedValue" bindings with "SelectedItem". The binding expressions can stay the same (although I don't think you need to specify the UpdateSourceTrigger).
Binding to SelectedItem means that your vmCustomer needs a selectedAddressCity property instead of the selectedAddressIdCity id (and you can remove cmbSelectedAddressCities' SelectedValuePath).

Sorry for the long delay.
I know I promised to post back my solution during the end of the week, but my pc decided to die the following day.
Regarding my original problem I adopted 2 "solutions".
First I removed the numeric ID changing it to the full description of the Region/City. I thought this was good, but the problem still remained for some "strange" cases (for example when clicking the first time on the Region combobox.
After digging a while in the code I discovered the real problem was in a converter I wrote for the application. During a conversion I made a mistake: instead of "if isnothing" I wrote "if not isnothing" and the resulting was a nothing converted to space, messing up the whole father-child relation.
As I was worried, the problem was in my code, and not in the piece of code I posted here.
I thanks again everyone, and apologize again for the delay

Related

wpf binding a TreeView to a SortedDictionary<string, List<Class>>

OK, once again, my Google-Fu isn't up to par and I would really appreciate a little guidance here.
I have a WPF app with multiple pages, one of which contains a TabControl, of which one of the tabs contains a grid, in which one of the columns contains a StackPanel with just two items on it: a Label and a TreeView. I have no need to update the TreeView once the content is obtained. The app uses MVVMLight (great toolkit!) to expose the data and said data is exposed in an mvvm (observable) property as it should be. I have checked and the data itself is available in correct form once I get to the point of setting the ItemsSource property so I know it's not the lack of data or the structure of the data itself. I have looked at all the entries on the web (at least the first 4 pages worth) matching the search terms "wpf treeview dictionary" and all articles come close, but don't get me to where I need to be. I'm missing something and what's worse, IntelliSense is even "helping" by providing the correct values for the xaml settings. So I know I'm close, but after two days of frustration, I'm throwing in the towel and asking for an assist.
So...to the meat of the problem: The data that the TreeView needs to display is in the form of SortedDictionary<string, List<ServerEntityNameMsSqlSvr>>. A ServerEntityNameMsSqlSvr class contains multiple properties, one of which is FullName. What I want the TreeView to display is the dictionary Key as the parent node and the FullName from each of the items in the List<ServerEntityNameMsSqlSvr>. You'd think that'd be simple, no? Not for me, the closest I can get is to display the Key of the dictionary, but either nothing for the children (best case) or throw an exception that stops the app with a null exception (worst case).
Here is the xaml I'm using (worst case):
<TreeView Grid.Column="0" ItemsSource="{Binding TableHierarchy}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value}">
<TextBlock Text="{Binding Path=Key}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="awe:ServerEntityNameMsSqlSvr">
<TextBlock Text="{Binding FullName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
"TableHierarchy" is the MVVM property that exposes the data, it is declared as SortedDictionary<string, List<ServerEntityNameMsSqlSvr>>. The DataType "awe:ServerEntityNameMsSqlSvr" is a simple class with a few methods and properties, nothing special. One layer of inheritance, no interfaces. Of the properties that are available, I want to expose just the FullName, which is declared as public string FullName => _FullName(); Yep, it calls an overloaded method to build the full name but the result is a simple string (and the method call happens when the data is built, not a display time, iow, the values are already there, at least debugging to the setter shows that the content is correct.
Some of the solutions that I have researched suggest that the data type be changed to something other than a dictionary. In this case, that's not possible and given that the lists are, on occasion, quite large, I don't want to rebuild it into something else. This needs to work with the sorted dictionary as declared.
The xaml shown above is indeed correct, however, the gadget that supports the data (the methods in the ServerEntityNameMsSqlServer class) all need to not throw exceptions under any circumstances. In this case, one of the methods not directly involved with the author's code but used somewhere else in the framework (an overloaded call to "Equals" that was constructed to check individual property equality to determine the result) was throwing a null exception because a property wasn't filled in for the particular use case.
Difficult to find, but that was the cause.

A way to bind differents values of a property in each DataGrid cell

I've searched on many threads but I couldn't find anything to solve my problem, and I don't really know what keywords I should use. I have a DataGrid which is populated by a DataSet, with columns that I specify manually.
In my column, 'Total', the Binding is set by :
<DataGridTextColumn Header="Total"
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}},
Path=DataContext.Total}"
The Total property is not in the DataSet, which is bound to my DataGrid, that's why I use this kind of Binding.
The problem is that when I set a value inside a cell of 'Total', like any user would do, this value is repeated in each cell of my column.
So, I know it is because of this property, since everytime I change my cell's value the Total property gets this new value and sends it back to my column. I could use a condition which uses the selected row's number or something, I have some ideas of what I should do, but I can't do that in my Xaml code.
I tried to create a Binding() in the code behind part but it doesn't really work and I'm not sure it could change anything.
Is there any way to use one value per row in this case ?
Thanks !
So, as Lee O. kindly said, I've first decided to alter my DataSet by adding a new column and some data inside. The problem was that I couldn't find any way to bind successfully on this second column in my designer code.
Anyway, I knew it wasn't the best way because I kind of prevented myself to modify something big to avoid big problems in my code and some other reasons, so I spent out a whole day to make it work better. I didn't think it could have taken so much time to modify it, but it finally worked.
The solution is to create (or alter in my case) a class which represents one row in your DataGrid, with as many properties as you need, including every columns you may want to have. Then, create an ObservableCollection (I really love this type of list, very useful and easy to use / update) in your ViewModel.
Finally, bind your column to your property in your class in your MainWindow like that :
// new ViewModel() + ObservableCollection<Class>
this.gridCameras.ItemsSource = viewModel.OCCameras;
this.Total.Binding = new Binding("Total");
If it can help someone too, I found something to bind correctly this class to a ComboBox in your DataGrid, which is a bit more complicated, here and here as I suppose that your ComboBox should be written like that (don't forget the x:Name property used in the foreach loop) :
<DataGridTemplateColumn Header="Fps" Width="80" x:Name="Fps">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cbFps"
Foreground="Black"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I strongly suggest you to add an Index property in your class, since I use that to identify the object in my ObservableCollection when I edit a cell / row and some other things. It's much better than to loop on your list until you find the element everytime you'd need it.
Hope it helps and feel free to ask any question you may have. I don't say I used the best way of the world, but it works for me and that's what I wanted.
Thanks again for your tips Lee O.

WPF: How to bind to only one item in a collection, not using ItemsControl since I don't want to display all of them

I have this requirement, that I have a collection of items (ObservableCollection), but I only want to display the first item. The requirement comes from the fact that in most of the case, the collection only contains one item. And due to the space limit, even if there is more than one items in the collection, we'd like to display the number of the items, details of the first one (same presentation as prior situation) and a ... symbol to indicate to the user that there is more items. And when the mouse is over the UI element a popup will eventually display all items.
The first solution I can think of (please suggest others if they are better) is to bind to this collection (but not using an ItemsControl) and define a DataTemplateSelector derived class (which return either the DataTemplate to display the only one item, or the DateTemplate which has the ... and the popup for more details, based on the number of items in the collection) and use it as ContentTemplateSelector.
But now my question: how both of my DataTemplate would look like in XAML, so that they can display only the first item in the collection? Obviously I can't have a ItemsControl.
UPDATE:
Now I have managed to make it work and agree this question can be closed (I can't delete it anymore since there is already some answers).
I actually knew how to bind to one certain item in the collection, but this was not where I am confused. I felt I should use ContentControl as one answer suggests. But I thought since I need to bind to the whole collection (not to single indexed item), and use a DataTemplateSelector to select the proper DataTemplate based on the number of items in the collection. The code would look like this:
<ContentControl Content="{Binding MyCollection}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
And in MyTemplateSelector I wasn't sure how to use it since there is no reference to my collection because it is defined as resource and it doesn't have the information of MyCollection. However, it turned out to be very simple, the DataTemplate can refer to an indexed item without knowing the name or any other reference. Simply like this:
<DataTemplate>
<TextBlock Text="{Binding [0].PropertyName}" />
<DataTemplate />
To bind to just one item from a collection, you can use the following syntax:
{Binding Items[0]}
Or to bind to a property of a single item from the collection:
{Binding Items[0].Property}
You can find out more about property path syntax from the Binding.Path Property page at MSDN... from the linked page:
• Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
Try this
<ContentControl Content="{Binding YourCollection[0]}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Ok, late to the party but I thought I'd share my 2 cents anyway: I'd better go with a dumber (XAML-)view and a view-model closer to your presentation needs.
Translated: instead of mapping your existing view-model (or raw data) and its collection of items directly to the view, I suggest to map that to an appropriate view-model showing something like a YourItemViewModel FirstItem property and a bool HasMore property. That second view-model would be easily unit-testable to make sure it behaves propertly, and would be easily mapped to a view with less logic, so to avoid possible hard-to-test problems in view.
{Binding Items[0].SomeProperty}
{Binding [0].SomeProperty}
{Path=/SomeProperty}

Combobox performance after changing ItemsSource

I have a datagrid with a column of comboboxes defined like this:
<DataGridTemplateColumn x:Name="AssortmentQualitySettingsDataGridColumn" Header="Kvaliteter">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=QualityInfoAssortmentCollection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=ActiveQuality}"></CheckBox>
<TextBlock Text="{Binding Path=QualityName}" IsEnabled="False"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
The ItemsSource is an ObservableCollection of objects. The texts for the textbox in the combobox is however editable in another datagrid and put in another ObservableCollection called QualityItemCollection, and to get the comboboxes in the grid above updated I have an event on the datagrid that fires when that collection is changed. This even causes the QualityInfoAssortmentCollection to be re-read (the combobox needs to be set again also, so there is some looping to get it working).
Now, what happens is that the first time when data is loaded, everything is nice and dandy, however, when the event updating QualityInfoAssortmentCollection has fired the comboboxes above takes 5-7 seconds to drop down when trying to get into it. The refresh itself I have timed and it takes less than a tenth of a second to do. The combobox doesn't have more than 8-10 rows and there no difference trying to use a virtualizingstackpanel as suggested elsewhere. The datagrid displaying it has around 10 rows so it's not even close to any enormous amounts of data that needs to be shuffled.
Edit: More explanation about not being able to use the defining QualityItemCollection. The QualityItemColletion is the same for all items in the above datagrid, but the information about which checkboxes should be checked is set per item in the grid above. Therefore I make a copy of QualityItemCollection into QualityItemAssortmentCollection which also has a bool for the checkbox. There might be a better way to do this?
Edit 2:
Tried the WPF profiler now and it locks up just as the program does and doesn't display anything during the time the program is doing strange things. However, it turns it's something Visual Studio does, since if I run the program alone and not through Visual Studio there is no delay. Yay.
Problem was with the VS debugger. It for some reason makes the combobox excruciatingly slow.
So, fix it?
when the event updating QualityInfoAssortmentCollection has fired the comboboxes above takes 5-7
seconds to drop down when trying to get into it.
Where does it spend time? It is not like there are no profilers around. It is totally possible that this is WPF related, in which case this link:
http://msdn.microsoft.com/en-us/library/aa969767.aspx
also gets you staretd with a WPF level profiler (i.e. you can see where WPF spends the time, which may be some mistake in some WPF definitions).
It is also possible you send too many nonsensible update events (ou should always c hange whether a value HAS vchanged before sending an update notification). So, an upate may updatea property to teh same value triggering another update. A profiler will allow you to find these occurances.
Noone here can help yuo - without code etc. But a profiler should make it QUITE obvious where the time is spent.

SelectedItem of SelectedItem

first of all I would like to thank you for the many good posts that i read in this forum. Unluckily I could not find anything of help for my current problem (either here or anywhere else).
What I'm trying to do sounds quite simple, but I have no clue how to get it to work ... perhaps I'm still to new to wpf or I don't thing wpfy enough :)
I'm designing a front end for a part in an automated manufacturing:
I have a quantity of places where pallets can be put (but it can be empty as well).
Each pallet has up to 3 places where parts can be mounted
Everything is created dynamically of a database and is reacting to changes.
The position of the parts on the pallet comes from the database as well and should be visualized
What I would like to have is:
An overview over the pallet-places with a preview of the pallet
When I select a place I want to see a detail view of the place
When I click on a part on the pallet of the detailed pallet I want to see details to the part
The first two points are quite simple and work nicely:
I have got a DataTemplate for every component (part, pallet, pallet-place). Actually those are UserControls that are imported as Datatemplates
the overview is a ListBox with the places as DataContext
for the detail-place-view I use the UserControl and bound it to the SelectedItem of the Listbox
I tried to bind the Text of a Textblock to the ID of the selected Part ... and fail.
Probably I could use some global variables in the code behind - but that sound very ugly.
Can anybody help?
I have got a solution ... it is not nice but works.
I created an event in the pallet, that triggers, when the selected part-place changes
I handle the event in the pallet-place and create a new one
And finally I handle it in the overview and change the detailview accordingly
Most likely there are much nicer solutions, but it will suffice.
Perhaps try an ElementName binding?
<TextBlock Text="{Binding ElementName=Name_of_your_Listbox, Path=SelectedItem.ID" />
Can you post a bit more code of your TextBlock and your Binding?
Context is important, if i use a ContentControl and bind its content to the SelectedItem like this:
<ContentControl Content="{Binding SelectedItem, ElementName=mylistbox}">
I can bind to the ID of the selected item in the DataTemplate like this:
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding ID}" />
</DataTemplate>
</ContentControl.ContentTemplate>
That is because setting the Content of the ContentControl automatically sets the DataContext as well, and this binding is relative to the DataContext since no source (ElementName, RelativeSource, Source) has been specified.
I do not know how your UserControl handles the context, if the DataContext is not affected such bindings will not work. You would need to bind directly then:
<uc:MyDetailsView Data="{Binding SelectedItem, ElementName=mylistbox}">
<!-- ... -->
<TextBlock Text="{Binding SelectedItem.ID, ElementName=mylistbox}" />
This of course defeats the purpose of having the binding on the UserControl itself in the first place. But unless you post some relevant code it's quite hard to tell what is wrong.
Also check the Output window in VisualStudio, binding errors will show up there and might provide valuable information as to what went wrong.

Resources