Silverlight control updates binded property when it should not - silverlight

I have a custom control in datagrid DataGridTemplateColumn.CellEditingTemplate:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Items}">
<DataGrid.Columns>
<data:DataGridTemplateColumn Header="Column1" >
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sdk:Label Content="{Binding Path=Description}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<common:MyControl SelectedObject="{Binding Path=SelectedItem, Mode=TwoWay, UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Inside of control I have exactly one place where I update SelectedObject dependency property:
BindingExpression expressionObject = GetBindingExpression(SelectedObjectProperty);
if (expressionObject != null)
{
expressionObject.UpdateSource();
}
This code is called on the LostFocus event.
Problem is: while UpdateSource method is called only once (checked with debugger) SelectedItem gets updated value(same) twice! I'm currently in loss since it's not supposed to be this way. Am I missing something?
Edit: I'm not using any MVVM framework. VS2012 debug callstack shows that first update is initiated by UpdateSource() call, but second just comes out of nowhere.

Related

WPF binding query - toggle button in a datagrid

I'm trying to create a column in a DataGrid containing a ToggleButton. The button's IsChecked property is bound to a property called IsSelected in my data item (implements INotifyPropertyChanged).
My first attempt failed to update IsSelected when the button is clicked, even though it correctly displayed the value when set elsewhere.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<ToggleButton IsChecked="{Binding IsSelected}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Searching around stackexchange, I found a couple of solutions:
Setting the UpdateSourceTrigger property of the binding
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<ToggleButton IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
or using relative binding
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.IsSelected}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
My question is, why does using direct binding require explicitly setting UpdateSourceTrigger, whereas the relative binding method does not?
This is because the DataGrid has an internal transaction behaviour, implemented by BindingGroup, and it generally won't update the source until the edit is committed. If your data object implements the IEditableObject interface, you will notice that its BeginEdit() and EndEdit() methods will be called when you edit a cell value.
Please refer to the following thread on the MSDN forums for more information about this: https://social.msdn.microsoft.com/Forums/vstudio/en-US/7f7196b8-b9dc-487d-93cd-e77f5b3d9906/confused-about-transactional-editing-edititemcanceledit?forum=wpf

WPF CellEditingTemplate and duplicated events

I've a WPF DataGrid with a DataGridTemplateColumn like this:
<DataGridTemplateColumn IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path=MyProperty, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged}"
TextChanged="ctl_TextChanged" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
I noticed that every time I go into a cell edit, a new textbox control is generated and, consequently, if I start typing a character, the TextChanged event is invoked several times...once for each instance of the control that was generated!
Sample project to reproduce the issue: TestEditingTemplate_4.5.2
I used the TextChanged event only for example, but the issue may also occur with other events (eg. events defined within a UserControl)
Is there is a way to avoid this behavior?
I wish to destroy the "edit" control on CellEditEnding, so that it does not interfere with the new control generated when I return to cell edit; how can I do?

Single-click-editable bound CheckBox in Xceed DataGridControl

I've spent the past hour searching for solutions to what should be a simple problem: how to create a single-click-editable bound CheckBox in Xceed's community DataGridControl.
To be clear: I want a CheckBox column where the user may click any CheckBox, regardless of what row is selected, and have the view model's IsSelected property changing accordingly.
Below is the latest permutation I've tried. This code reads the value from the model, but clicking the CheckBox does not call the IsSelected setter for some reason.
<xcdg:DataGridControl x:Name="DictionariesDataGridControl" ItemsSource="{Binding Mode=OneWay, Source={StaticResource DictionariesViewSource}}" AutoCreateColumns="False" AutoRemoveColumnsAndDetailConfigurations="False" SelectionMode="Extended" NavigationBehavior="RowOnly">
<xcdg:DataGridControl.View>
<xcdg:TableView UseDefaultHeadersFooters="False" ShowRowSelectorPane="False" VerticalGridLineThickness="0">
<xcdg:TableView.FixedHeaders>
<DataTemplate>
<xcdg:ColumnManagerRow BorderThickness="0"/>
</DataTemplate>
</xcdg:TableView.FixedHeaders>
</xcdg:TableView>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="IsSelected" MinWidth="20" MaxWidth="20" CellEditorDisplayConditions="RowIsCurrent">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding ., Mode=OneWay}" IsHitTestVisible="False"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
<xcdg:Column.CellEditor>
<xcdg:CellEditor>
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
</xcdg:Column.CellEditor>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
EDIT 1
I'm trying this, which does exactly what I need:
<xcdg:Column FieldName="IsSelected" MinWidth="20" MaxWidth="20" CellEditorDisplayConditions="Always"/>
except that, for some reason, the CheckBox is styled with a blue background!
I've selected the element in the visual tree that has a Background property defined as SolidColorBrush with #FF0000FF as the color:
EDIT 2
I decompiled the DataGridCheckBox class that Xceed uses to render the CheckBox and found this override:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ChildCheckBox.Background = (Brush) new SolidColorBrush(Colors.Blue);
}
What a bizarre decision by Xceed to arbitrarily set the background color to blue.
EDIT 3
Using #JBrooks' answer, I tried the following:
<xcdg:Column FieldName="IsSelected" MinWidth="20" MaxWidth="20" CellEditorDisplayConditions="Always">
<xcdg:Column.CellEditor>
<xcdg:CellEditor>
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding ., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
</xcdg:Column.CellEditor>
</xcdg:Column>
Unfortunately, for some reason the setter on the IsSelected property is never called when I check the box. The getter is called several times, though, and the CheckBoxes are appearing correctly on initial bind.
You have both a CellContentTemplate and a CellEditor, so the first click does the "go into edit mode" logic. Just have a single one like below. This is for a normal WPF DataGrid, but maybe you can try something similar for your grid.
<DataGridTemplateColumn Header="Active" SortMemberPath="IsActive" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsActive, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding IsEnabled}" Style="{StaticResource rightsCB}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
For this DataGrid I also have these properties set:
SelectedItem="{Binding SelectedUser, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionUnit="FullRow" SelectionMode="Single"
So this DataGrid does act like what you want - I click the checkbox in row 4 and the IsChecked changes and it also makes row 4 the current row which sets the SelectedUser to the user bound to row 4.

Silverlight ComboBox SelectionChanged event fires twice

I'm using a ComboBox within a DataGrid. I am using this DataGrid for both "Add" and "Edit". When I change the value of ComboBox in code during "Edit", the SelectionChanged Event gets fired twice. 1st time it assigns the proper value, then 2nd time null is assigned to ComboBox which clears the data I had set previously!!
I can't figure out what exactly I'm doing wrong.
Here's the XAML snippet where I bind the ComboBox to model.
<sdk:DataGridTextColumn x:Name="SlNo" Binding="{Binding SlNo}" Header="sl.no" IsReadOnly="True"/>
<sdk:DataGridTemplateColumn Header="Activity Type">
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="ActivityTypeCombo" IsDropDownOpen="True"
ItemsSource="{Binding AvailableActivityTypes}"
SelectionChanged="ActivityTypeSelectionChanged"
SelectedItem="{Binding SelectedActivityType, Mode=TwoWay}"
SelectedValue="{Binding Path=Description, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
The Code Snippet where I set the value is:
foreach (var claimDetailViewModel in Claims)
{
claimDetailViewModel.SelectedActivityType =
_autoFillModel.ActivityTypes.SingleOrDefault(at => at.Id == climDetailViewModel.ActivityTypeId);
}
ClaimDetailsGrid.ItemsSource = Claims;
It seems to me that using both SelectedItem and SelectedValue to control selection will not work properly.
When SelectedItem is set this will most likely trigger SelectedValue to be evaluated, and because there is no SelectedValuePath set it will result in SelectedItem=null.
Try removing the SelectedValue binding.

WPF DataGrid, in a Binding in a ColumnTemplate access a Element outside of the DataGrid

I'd like to access a Object from my UserControl from within the Datgrids ColumnTemplate.
This doesn't work. Now I've read it's because of the Datacontext.
I found this Example, which should fix this: http://blog.errorok.com/2010/09/09/212/
But the Event: ColumnDataContextChanged is never called in my Project!
here's a part of my XAML:
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ConfigurationTool:EditProtocolDatasets}}, Path=grdDatasets.SelectedItem.Storage.DatabaseFieldTypes}"
SelectedItem="{Binding DatabaseFieldType}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
ConfigurationTool:EditProtocolDatasets is my UserControl, grdDatasets is another Datagrid, to which SelectedItem I'd like to bind!
Okay, I'm going to suggest a completely different direction than my first one. My guess is that you have the ItemsSource for grdDatasets bound to something.
For the item that's going to act as your datacontext for the control, make sure it has the following characteristics, or at least a comparable structure:
public class ListOfDataSets : DependencyObject
{
public IEnumerable<DataSetOption> Items
{
get
{
...Whatever you normally use to get your DataSetOptions...
}
}
public DataSetOption SelectedItem
{
get { return (DataSetOption)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(DataSetOption), typeof(ListOfDataSets), new PropertyMetadata(null));
}
The key here is that you have one property that is a list of your choices, and another property that represents one of those items.
Then, in your XAML, your control can have the following structure:
<UserControl>
<UserControl.Resources>
<ConfigurationTool:ListOfDatasets x:Key=DataSetOptions />
</UserControl.Resources>
<StackPanel Name="LayoutRoot">
<DataGrid Name="grdDatasets"
ItemsSource="{Binding Source={StaticResource DataSetOptions}, Path=Items}"
SelectedItem="{Binding Source={StaticResource DataSetOptions}, Path=SelectedItem}"
...
</DataGrid>
...
<DataGrid Name="OtherDataGrid" ItemsSource="{Binding OtherSource}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource DataSetOptions}, Path=SelectedItem.Storage.DatabaseFieldTypes}" SelectedItem="{Binding DatabaseFieldType, Mode=TwoWay}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</Datagrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
I actually tried this structure out, and the databinding works fine. If the DataSetOptions change a lot, though, this solution may not work, unless you're using MVVM, and the ViewModel is good at tracking what options are available, and presents them properly to the View.
Hopefully this makes sense. I actually tried this one before answering.
I was not correct with my original answer, and overestimated the capabilities of RelativeSource before I experimented with it.
---Original text below---
I think I can help, but I need a few more details. For now I'm just going to work off assumptions.
Assumption 1: You're in a WPF UserControl with a DataGrid that has a defined ItemsSource.
Assumption 2: The UserControl has another element that you want a column within your DataGrid to have access to.
If these two assumptions are correct, it is a much better problem to have in WPF than in Silverlight.
Each row in your DataGrid is going to be working from within a DataContext that consists of the Item for that row. But, you can reach outside of the cell's (or any) DataContext with a RelativeSource.
So, if you wanted to go up the Visual Tree to get to your control's Width, you would use:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyUserControl}}, Path=Width}
This will trace upward in the Visual Tree until an object of type "MyUserControl" is found, at which point it will grab the "Width" property and bind to it.
The Path doesn't have to be only one item deep, either. You can run up an down your visual tree as required. As this gets more complex, though, your code is going to be more fragile.
If this isn't correct, please post your XAML (or something similar) and say so, and I'll spin up a test environment and edit my post.

Resources