using wpf datagridcomboboxcolumn's IsSynchronizedWithCurrentItem - wpf

(see below for my own answer that I came up with after letting this percolate for days & days)
I am trying to achieve the following scenario in WPF.
I have a datagrid that is displaying rows of data for viewing and additional data entry. It is a new app but there is legacy data.
One particular field in the past has had data randomly entered into it. Now we want to limit that field's values to a particular list. So I'm using a DataGridComboBoxColumn. FWIW I have alternatively tried this with a DataGridTemplateColumn containing a ComboBox.
At runtime, if the existing value is not on the list, I want it to display anyway. I just cannot seem to get that to happen. While I have attempted a vast array of solutions (all failures) here is the one that is most logical as a starting point.
The list of values for the drop down are defined in a windows resource called "months".
<DataGridComboBoxColumn x:Name="frequencyCombo" MinWidth="100" Header="Frequency"
ItemsSource="{Binding Source={StaticResource months}}"
SelectedValueBinding="{Binding Path=Frequency,UpdateSourceTrigger=PropertyChanged}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsSynchronizedWithCurrentItem" Value="False" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
What is happening is that if a value is not on the list then the display is blank. I have verified at runtime that the IsSynchronizedWithCurrentItem element is indeed False. It is just not doing what I am expecting.
Perhaps I am just going down the wrong path here. Maybe I need to use a textbox in combination with the combobox. Maybe I need to write some code, not just XAML. I have spent hours trying different things and would be really appreciative of a solution. I have had a few suggestions to use this class or that control but without explanation of how to use it.
Thanks a bunch!

I have finally solved this.
The trick is to get rid of the comboboxcolumn and use a template that has a textbox for display and a combobox for editing. However, I still spent hours with a new problem...when making a selection in the combobox, it would modify any other rows where I had also used the combobox in the grid. Guess what solved the problem! The IsSynchronizedWithCurrentItem property that I was trying to use before. :)
<DataGridTemplateColumn x:Name="frequencyCombo" Header="Frequency">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Frequency}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Source={StaticResource frequencyViewSource},
TargetNullValue=''}"
SelectedItem="{Binding Path=Frequency}" IsSynchronizedWithCurrentItem="False"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
No ugly hacks. No non-usable choices hanging around at the bottom of the dropdown. No code to add those extra values and then clean them up.
I am not going to take away the "Answer" on Mark's suggestion since it enabled me to get the app into my client's hands, but this is the solution I was looking for. I found it buried in a "connect" item after hours of web searching.
Thanks for everyones help!

Could you please clarify what's happening here? It's unclear what the "existing value" is at runtime - if this is the field where data is being randomly entered, by limiting it does this mean you're running some sort of validation logic although you still want it to display?
Also, I'm more on the Silverlight side of things...does WPF also default to one way binding?

Rather than mixing the static resource and the view model property as a source for items on the list, have you tried using an ObservableCollection or CollectionViewSource in the view model? Then you could insert and remove the non-standard items at will and make them selected (or not) whenever you want. So the "normal" list would have the normal months, but when an odd one comes along, add that to the list and make it selected. Seems like it would be easier to control exclusively in the view model.

Why not just do something like:
//create collection
PagedCollectionView view = new PagedCollectionView(e.Result);
view.SortDescriptions.Add(
new SortDescription("Months", ListSortDirection.Ascending));
gridProducts.ItemsSource = view;
//filter collection by category
PagedCollectionView view = new PagedCollectionView(e.Result);
view.Filter = delegate(object filterObject)
{
Product product = (Product)filterObject;
return (product.CategoryName == "Legacy");
};
gridProducts.ItemsSource = view;
//create categories through grouping
PagedCollectionView view = new PagedCollectionView(e.Result);
view.GroupDescriptions.Add(new PropertyGroupDescription("Legacy"));
view.GroupDescriptions.Add(new PropertyGroupDescription("etc..."));
gridProducts.ItemsSource = view;

Try this:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Months}"
Text="{Binding Value}"
IsEditable="True" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Related

WPF TabControl and DataGrid bugs, bugs and bugs

for everyone, I found different problems with WPF, the TabControl and the DataGrid. Especially if the TabControl ItemsSource is bound.
Problems i found:
Selection in DataGrid is not visible after switch Tabs back and forth
DataGrid looses sorting on tab switch (SortDescriptions of CollectionView.GetDefaultCollection is cleared on unload)
if a DataGrid cell has focus (is in edit mode) and you click on another tab, two things can happen: 1.) the bound object will not be updated; 2.) if the object is invalid you receive an error DeferRefresh not allowed during edit, or something like this
DataGridComboBox and possibly other controls do clear their values if you switch to another tab if you are working with bound TabControls and DataTemplates. This clears any selection.
So now my question: Is there any ThirdParty controls which perform better in this scenarios?
You also can vote here http://connect.microsoft.com/VisualStudio/feedback/details/807849/databound-tabcontrol-bugs
I got answer from Microsoft it won't fix because not enough people have this problems.
I know some fixes, but they are some really not clean (f.e. using reflection). Maybe you have some ideas?
Hmmm, interesting post though I bet there are all not bugs. I think Microsoft hasnt even taken a look at those things. They might never do. I would appreciate it much if you could post or upload code of all those issues you might be thinking they are all buggy bugs.
Btw, what do you mean with TabControl ItemsSource is bound?
Here is my feedback on this from the info you gave us in the question. 1) You select something, you click away anywhere, no matter if tabitem or another window, you will lose focus, the selection will turn inactive means to slightly grey color. 2) Unloading means removing a control from VisualTree and so CollectionView must be cleared to release memory. This is good so since you dont want memory leaks. 3) If the cell's edit template contains controls which shall update the binding's source on focus lost then for sure that will happen. If you happen to be using a template for TabItems then the template will be mostly reused (means with same instance) and so you might end up taking the seat away from DataGrid' ass which is futhermore not a bug but rather something you wouldnt like to happen to you either. Therefore the DataGrid might be yelling "yo, no fooling around while I am editing a cell". 4) Same as in #3 it depends what you doing and how you define the templates. Consided mostly if template is in resources with a key then the template will be reused.
Just post us code please and let us take a look. I bet you might be doing something very "wpf-unlikely". :)
If those things really happen to be "buggies" (others review the same behavior), I bet there are workarounds for them. :)
Personally I have a feeling that all those things happen because you are using data bound TabControl. Whatever that might mean. I am excited to see what are data bound TabControls and how are they bound? How do you define those templates.
I have the same issue.
Fix to DataGridComboBox issue may be to specify the ItemsSource of the ComboBox as the DataContext property of the TabControl instead of the DataGrid as the DataGrid is removed from the visual tree when you select another tab:
<TabControl x:Name="tabControl" Behaviours:TabContent.IsCached="True">
<TabItem Header="Tab1" Content="{Binding}" ContentTemplate="{StaticResource Tab1}"/>
<TabItem Header="Tab2" Content="{Binding}" ContentTemplate="{StaticResource Tab2}"/>
</TabControl>
<DataTemplate x:Key="Tab1">
<DataGrid ItemsSource="{Binding Entities}" x:Name="dataGrid">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="100"/>
<DataGridTemplateColumn Header="Position" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Position}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Position, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=DataContext.Positions, ElementName=tabControl}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>

Prevent DataGrid row from being deleted

I want to protect some rows of a DataGrid to be protected from deletion by the user, although the property CanUserDeleteRows is set to true.
Is there a possibility to protect some rows, hopefully through a databinding or a trigger? The ItemsSource is bound to an ObservableCollection of T.
If you have a property on your bound objects that can be used to determine if the current row can be deleted , like 'IsDeleteEnabled', then you can bind the DataGrid's CanUserDeleteRows property to SelectedItem.IsDeleteEnabled.
For example,
<DataGrid Name="dataGrid1"
CanUserDeleteRows="{Binding ElementName=dataGrid1, Path=SelectedItem.IsDeleteEnabled}"
Never done this with a DataGrid. Usually, when I need to control something like this, I use a ListBox and a DataTemplate with a Grid within to give it the idea of a Grid or a ListView with a GridView in the template because they both give you more control over the interaction.
A shot in the dark, since you're binding, you could use a DataGridTemplateColumn.CellEditingTemplate and make your own Delete button/text which is visible or enabled based off logic within your binding object. Maybe something like this (I didn't test this, but it should be a direction you can head)?
<dg:DataGridTemplateColumn Header="Action">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Text Content="Delete" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ButtonEnabled="{Binding Path=IsDeleteEnabled, Mode=OneWay}" Content="Delete" Command="{Binding Path=DeleteMe}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>
Using this method, since the command is bound to the individual object, you would probably have to raise an event your screen's ViewModel handles to remove that row from the ObservableCollection.
Again, not sure if this is the best way, but it's my 10 minute stab at it. So if it's horrible, please don't vote me down too much.

Cross DomainDataSource Combobox SelectedItem Binding

I'm fairly new to Data binding & XAML, so this probably is fairly simple thing but I've been stumped on it for days now (and frustrated with more googling than i can track at this point) and would appreciate any pointers in the right direction. My only preference is to keep it in pure XAML if possible.
In my RIA SL4 project, I have two Entities PackageOS and OS where PackageOS has an association to OS through PackageOS.OS (associating through PackageOS.OSID <-> OS.ID - and [Include] + .Include() setup properly on relevant sections)
This is the template (defined in Page.Resource section along with all other involved DDS) I'm using in DataForm to get OSEntities List to bind into PackageOS Entity (coming from RIA GetOSEntities() using DDS):
<DataTemplate x:Key="POSItemTemplate">
<StackPanel>
<toolkit:DataField Label="PackageOS.OS">
<TextBlock Text="{Binding Source={StaticResource packageOSEntityDomainDataSource}, Path=Data.CurrentItem.OS}" />
</toolkit:DataField>
<toolkit:DataField Label="OS">
<ComboBox ItemsSource="{Binding Path=Data, Source={StaticResource osEntityDomainDataSource}}"
SelectedItem="{Binding Path=Data.CurrentItem.OS, Source={StaticResource packageOSEntityDomainDataSource}}"/>
</toolkit:DataField>
</StackPanel>
</DataTemplate>
The core problem is SelectedItem of ComboBox is not working. All the bindings are reachable from IDE Binding wizard so it's not a problem of me typing incorrect path. I can see packageOSEntityDomainDataSource.Data.CurrentItem to be of type PackageOS.
If i create a manual entry in backend database, the result is shown in PackageOS.OS textblock so I know it is properly being returned but SelectedItem refuses to pick it up (it ends up selecting the first value in dropdown list regardless of OS item in PackageOS).
Many thanks in advance!
Finally figured this out. Leaving my answer in hopes that it saves somebody else the time that I spent on this.
First Lesson
The problem was in the fact that I didn't have a custom Equality implementation for generated entities and default reference equality didn't work as I was using two different instances. Once I implemented IEquatable on my generated entities (through .shared.cs partial classes on server side) everything started working like a charm.
For details please see Silverlight ComboBox Control Population by Manishdalal
Second lesson
Do not use multiple DDS controls if you can help it. Especially once you use a write operation on a DDS, you cannot load/refresh any other DDS that is sharing the DomainContext until changes are committed. The link above shows how to avoid multiple DDS by using list generators when all you want is to pick up list of entities to fill ComboBox up.
My new code looks like this:
<DataTemplate x:Key="POSItemTemplate">
<StackPanel d:DataContext="{Binding Source=packageOSDomainDataSource, Path=Data.CurrentItem}">
<toolkit:DataField Label="OS">
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=OSList, Source={StaticResource OSListGenerator}}"
SelectedItem="{Binding Path=OS, Mode=TwoWay}" />
</toolkit:DataField>
</StackPanel>
</DataTemplate>
Where OSListGenerator is returning an IEnumerable<OSEntity> through its OSList property after loading it from DomainContext
Third Lesson
In DDS DataTemplate you have to be explicit with TwoWay Binding. This is the new behaviour; something that took me days to figure as most of the tutorials I referred to were using SL3 and I didn't realize that this was a breaking change in DDS DataTemplate behaviour in SL4.

ADO Entity Framework 4 to WPF Datagrid. DatagridComboBox nightmare

The WPF datagrid -seems- like it's going to work, but the combobox implementation does not work straight from the designer. So I'm left wandering around in the XAML randomly changing things trying to get it to work.
The problems are numerous. I want to display a foreign key relationship (with drop down) instead of a bunch of numbers for a selection. It seems like it shouldn't be this hard.
I can get the right values to show up (their description instead of an ID), but the table freaks out thinking that all the values have been modified. If I select a drop down, it refuses to allow me to edit anything else.
I want to chalk this up as a .NET bug, but since I'm new to WPF datagrids, it's probably just me. Here is the code.
<DataGridComboBoxColumn Header="Make Up" ItemsSource="{Binding Source={StaticResource materialMakeUpTypesViewSource}}"
DisplayMemberPath="Description" TextBinding="{Binding Path=MaterialMakeUpType.Description}"
SelectedItemBinding="{Binding Path=MaterialMakeUpType.Description}" SelectedValueBinding="{Binding Path=MaterialMakeUpType.ID}" />
This was just confusing because of the different options.
ItemSource was right.
DisplayMemberPath, right.
TextBinding wasn't needed and actually caused a lot of visual artifacts.
SelectedItemBinding wasn't needed.
I had to add a SelectedValuePath.
Anyway, this works as intended.
<DataGridComboBoxColumn Header="Make Up" ItemsSource="{Binding Source={StaticResource materialMakeUpTypesViewSource}}"
SelectedValuePath="ID"
DisplayMemberPath="Description"
SelectedValueBinding="{Binding Path=makeup}" />

WPF: Can I bind to a method of the selected object in another control?

I have two WPF list boxes. One is a list of lists (actually a List of ObservableCollection), the other is a list of all known instances of "Thingy".
Here's the datatemplate I'm using for the "thingy" class.
<DataTemplate DataType="{x:Type Model:Thingy}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="ThingyInListCheckBox" Click="ThingyInList_Click"></CheckBox>
<TextBlock Text="{Binding Path=ThingyName}"></TextBlock>
</StackPanel>
Here's the XAML for the list boxes:
<ListBox
Name="ListOfGroups"
SelectionMode="Single">
</ListBox>
<ListBox
Name="ListOfThingys"
SelectionMode="Single">
</ListBox>
I have the data binding for the list boxes set up in code, because I'm too tired to figure out how to do it in XAML:
ListOfGroups.ItemsSource = InMemoryCache.ThingyGroups;
ListOfThingys.ItemsSource = InMemoryCache.Thingys;
What I want is the checkbox "ThingyInListCheckBox" to be checked if the 'thingy' object is in the list that is the selected item in the "ListOfGroups" listbox. So basically I need to bind it to the "Contains" method of the "ListOfGroups".SelectedItem while passing it the "ListOfThingys".SelectedItem as a parameter.
I'm tempted to do this all in code, but I'm trying to get a better understanding of XAML data binding because I hate myself and I want me to suffer.
Is this even possible, or have I hit the inevitable "wall of databinding" that exists in every other data binding system in the history of software development?
It is possible, in fact the hard thing is that there are many ways to do this and you have to choose one. None of them is a simple addition to your current code. However there is one way, by which you gain more than solving your problem. Actually, it is more of a pattern, called MVVM (some might argue about the naming).
Here is a small explanation on your example.
Suppose ThingyGroup has an IsSelected property, which is bound to the IsSelected property of the containing ListBoxItem. Again, suppose Thingy has a Group property too. Then you can use Group.IsSelected as a path to bind checkbox. Notice that there is still a small issue that IsSelected is a bool and IsChecked is a nullable bool.
A search on MVVM should give you concrete samples.

Resources