Silverlight Checkbox inside datagrid causing Trouble - silverlight

Hi all i came across a problem with the checkboxes inside the datagrid.
First let me post my code of what i have achieved and then i will tell what's causing the trouble
This is the code of my datagrid inside a child window
<Controls:DataGrid x:Name="dgAthlete" Height="Auto" Width="Auto"
IsReadOnly="True" AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Data, ElementName=dds}"
>
<Controls:DataGrid.Columns>
<Controls:DataGridTemplateColumn Header="CheckBoxColumn">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="cbAddAthlete" IsChecked="{Binding IsAdded}" Tag="{Binding}"
IsEnabled="True" Checked="cbAddAthlete_Checked" Unchecked="cbAddAthlete_Unchecked" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsAdded,Mode=TwoWay}"/>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
<Controls:DataGridTemplateColumn>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding ImageFileName}"
Width="25" Height="25"
HorizontalAlignment="Stretch" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
</Controls:DataGridTemplateColumn>
<Controls:DataGridTextColumn Header="FirstName" Binding="{Binding FirstName}" />
<Controls:DataGridTextColumn Header="MiddleName" Binding="{Binding MiddleName}"/>
<Controls:DataGridTextColumn Header="LastName" Binding="{Binding LastName}"/>
<Controls:DataGridTextColumn Header="Email" Binding="{Binding Email}" />
<Controls:DataGridTextColumn Header="DOB" Binding="{Binding BirthDate}"/>
<Controls:DataGridTextColumn Header="Phone" Binding="{Binding PhoneNumber}"/>
<Controls:DataGridTextColumn Header="Website" Binding="{Binding WebSite}"/>
<Controls:DataGridTextColumn Header="Team" Binding="{Binding TeamName}"/>
<Controls:DataGridTextColumn Header="Club" Binding="{Binding ClubName}"/>
<Controls:DataGridTextColumn Header="County" Binding="{Binding CountyName}"/>
</Controls:DataGrid.Columns>
</Controls:DataGrid>
<Controls:DataPager x:Name="dpAthlete" PageSize="4"
HorizontalAlignment="Stretch" Source="{Binding Data, ElementName=dds}"
Width="Auto"/>
</StackPanel>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
The code behind for this is
public partial class AddAthletesToCompetition : ChildWindow
{
public ObservableCollection<Athlete> Athletes { get; set; }
public int CompetitionId { get; set; }
private PagedCollectionView pvcAthlete;
public AddAthletesToCompetition(int competitionId)
{
InitializeComponent();
CompetitionId = competitionId;
LoadAthlete();
Athletes = new ObservableCollection<Athlete>();
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
private void LoadAthlete()
{
var context = new PublicServiceClient();
context.GetAthletesNotInCompetitionCompleted += context_GetAthletesNotInCompetitionCompleted;
context.GetAthletesNotInCompetitionAsync(CompetitionId);
}
void context_GetAthletesNotInCompetitionCompleted(object sender, GetAthletesNotInCompetitionCompletedEventArgs e)
{
if (e.Result != null)
{
pvcAthlete = new PagedCollectionView(e.Result.ToList());
dgAthlete.ItemsSource = pvcAthlete;
dpAthlete.Source = pvcAthlete;
}
}
//Checkbox Checked Event Hanlder
private void cbAddAthlete_Checked(object sender, RoutedEventArgs e)
{
var athlete = ((CheckBox)sender).Tag as AthleteBO;
if (athlete != null)
{
var ath = new Athlete();
ath.AthleteId = athlete.AthleteId;
Athletes.Add(ath);
}
}
//CheckBox unchecked Event Handler
private void cbAddAthlete_Unchecked(object sender, RoutedEventArgs e)
{
var athlete = ((CheckBox)sender).Tag as AthleteBO;
if (athlete != null)
{
var item = Athletes.First(i => i.AthleteId == athlete.AthleteId);
Athletes.Remove(item);
}
}
}
As you can see i am using paging everything works fine i.e check and uncheck events for the checkbox work fine when we are on the first page but let's say you checked the first and the second item in the grid on the first page , now as soon as i go to the next page what my grid is doing its retaining the previous view and checking by default the first and the second item on the second page which is not expected behaviour
I went through various post on this issue and found that the problem is that the view is injected by the datagrid but somehow i couldn't find any solution.
What other's were talking about was refreshing the observable collection and again binding that to my grid.
So i was a little confused as where to bind that and when to update the observable collection.
I have posted whole of the code and whenever you reply please mention where exactly should i do the changes for this to work.
For a note i am using WCF and EF , so the list of athletes which i have here i will be sending this list to the WCF service where using EF it will be inserted into the database.
I know it's not a bug in Silverlight but this added feature of grid virtualization is causing a trouble with me currently so there should aslo be some solution to this problem.
Thanks

Add a IsSelected bool property to your entity and bind the checkbox control to it. This code sample looks like you are using .NET RIA services and the DomainDataSource (ItemsSource="{Binding Data, ElementName=dds}") control, not WCF? I have used this solution with WCF, but not in a paged setup. Give it a try and let us know how it turns out,

I was binding my combobox but was still getting the problem, till I started using two way binding, seems to be gone now

Related

How to get the checkbox column sender's datagrid parent

Hi I have a datagrid (xaml code below):
<DataGrid.Columns>
<DataGridTextColumn Header="FLEET" IsReadOnly="True" Width="1*" Binding="{Binding FLEET, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTemplateColumn Header="SELECTED?" Width="1*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Viewbox Margin="-0.2" Height="18.5">
<CheckBox IsThreeState="True" IsChecked="{Binding Path=isSelected, UpdateSourceTrigger=PropertyChanged}" Click="FSC_CheckBox_Click"/>
</Viewbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
The click events:
private void FSC_CheckBox_Click(object sender, RoutedEventArgs e)
{
......
}
Is there a way I can get the the parent datagrid object from the click sender? I've tried this link
Get containing row from a CheckBox inside of a DataGrid, but it didn't work because of the viewbox and datatemplate(I guess).
Any suggestions is appreciated. Thank you.
The click event handler will get the sender, which you can cast to checkbox.
I reckon the simplest approach is to abuse the Tag property on your checkbox.
That can be any old object you fancy.
You can use a relativesource binding for that.
Something like:
<CheckBox Tag="{Binding RelativeSource={Relativesource AncestorType=DataGrid}}"
Grab it off there, roughly:
private void FSC_CheckBox_Click(object sender, RoutedEventArgs e)
{
var cb=(CheckBox)sender;
var parentDataGrid = (DataGrid)cb.Tag;
}
This is air code, I don't have a datagrid with a checkbox and a click handler to try it out with easily so there may be some typo lurking.

wpf DataGrid RowDetailsTemplate Scroll

<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}"></DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Template="{DynamicResource TemplateDataGridPrintAndExport}"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid/>
I have a datagrid like above. Datgrid's row detail template also contains a datagrid. Inner datagrid is filled when the parent one's columns are clicked. My problem is this : if the row detail template datagrid is fulfilled and user mouse hovers on it while scrolling parent datagrid the scroll is not working. User should hover the mouse to the main datagriid to scroll. However, it is not user friendly. How can I prevent inner datagrid behaving in such a way?
I found the soultion by trying alternatives :
<DataGrid ScrollViewer.CanContentScroll="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}"></DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Template="{DynamicResource TemplateDataGridPrintAndExport}" IsReadOnly="True" ScrollViewer.CanContentScroll="False" IsEnabled="False"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid/>
The solution is to give ScrollViewer.CanContentScroll="False" attribute to the outer data grid and IsReadOnly="True" ScrollViewer.CanContentScroll="False" IsEnabled="False" attributes to inner datagrid. Now it is scrolling smoothly and accoording to the parent datagrid.
I would like to propose two alternative solutions, since the chosen one has serious side effects. One of them mentioned by Kaizen - you lose ability to interact with nested DataGrid and its child controls. Second one is the change of appearence of controls in their disabled state.
Change IsReadOnly="True" to IsHitTestVisible="False" in osmanraifgunes' solution. This will fix the appearence side effect, but you still won't be able to interact with inner controls (using mouse). Code:
<DataGrid ScrollViewer.CanContentScroll="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid
IsHitTestVisible="False"
ScrollViewer.CanContentScroll="False"
Template="{DynamicResource TemplateDataGridPrintAndExport}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Catch the tunneling PreviewMouseWheel event in the control within RowDetailsTemplate and pass it back to parent as a bubbling event. This will effectively make controls within RowDetailsTemplate blind only to mouse scrolling, and allow controls above in visual tree to handle it however they want to.
xaml:
<DataGrid ScrollViewer.CanContentScroll="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid
PreviewMouseWheel="DataGrid_PreviewMouseWheel"
Template="{DynamicResource TemplateDataGridPrintAndExport}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
code behind:
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Handled)
{
return;
}
Control control = sender as Control;
if(control == null)
{
return;
}
e.Handled = true;
var wheelArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = MouseWheelEvent,
Source = control
};
var parent = control.Parent as UIElement;
parent?.RaiseEvent(wheelArgs);
}
If you're using .NET 4.5 and above you can use VirtualizingPanel.ScrollUnit="Pixel" on the outer grid, which will allow you to scroll by pixels instead of units (items) as that is causing pretty weird behavior when having big inner DataGrid as it starts jumping around.
Then you can just past scrolling event to the parent using PreviewMouseWheel event on the inner DataGrid since it is being captured by the inner control.
Xaml:
<DataGrid VirtualizingPanel.ScrollUnit="Pixel">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid PreviewMouseWheel="DataGrid_PreviewMouseWheel"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
cs:
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var parent = ((Control)sender).Parent as UIElement;
parent?.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = MouseWheelEvent,
Source = sender
});
}
I've used #Bartłomiej Popielarz's 2nd approach to make it work.
In my case somehow control.Parent always returned null. Thats why I've changed the according line to
var parent = VisualTreeHelper.GetParent(control) as UIElement;
Also I've created a attached Property that does the forwarding (better suited for MVVM approaches).
public class FixScrollingBehaviorOn
{
public static readonly DependencyProperty ParentDataGridProperty = DependencyProperty.RegisterAttached("ParentDataGrid", typeof(DataGrid), typeof(FixScrollingBehaviorOn),
new FrameworkPropertyMetadata(default(DataGrid), OnParentDataGridPropertyChanged));
public static bool GetParentDataGrid(DependencyObject obj)
{
return (bool)obj.GetValue(ParentDataGridProperty);
}
public static void SetParentDataGrid(DependencyObject obj, bool value)
{
obj.SetValue(ParentDataGridProperty, value);
}
public static void OnParentDataGridPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid == null)
{
throw new ArgumentException("The dependency property can only be attached to a DataGrid", "sender");
}
if (e.NewValue is DataGrid parentGrid)
{
dataGrid.PreviewMouseWheel += HandlePreviewMouseWheel;
parentGrid.SetValue(ScrollViewer.CanContentScrollProperty, false);
}
else
{
dataGrid.PreviewMouseWheel -= HandlePreviewMouseWheel;
if (e.OldValue is DataGrid oldParentGrid)
{
oldParentGrid.SetValue(ScrollViewer.CanContentScrollProperty, ScrollViewer.CanContentScrollProperty.DefaultMetadata.DefaultValue);
}
}
}
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Handled)
{
return;
}
var control = sender as DataGrid;
if (control == null)
{
return;
}
e.Handled = true;
var wheelArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = control
};
var parent = VisualTreeHelper.GetParent(control) as UIElement;
parent?.RaiseEvent(wheelArgs);
}
Feel free to use this as follows on your inner DataGrid. Note that The ScrollViewer.CanContentScrollProperty is being set from within the AttachedProperty, which may not be everyone's favourite approach.
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid
attachedProperties:FixScrollingBehaviorOn.ParentDataGrid="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
Template="{DynamicResource TemplateDataGridPrintAndExport}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Xaml :
<DataGrid ScrollViewer.CanContentScroll="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=test}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid
PreviewMouseWheel="DataGrid_PreviewMouseWheel"
Template="{DynamicResource TemplateDataGridPrintAndExport}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Code behind :
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
DataGrid dg = new DataGrid();
if (sender is DataGrid)
{
dg = (DataGrid)sender;
}
dg.IsEnabled = false;
await Task.Delay(200);
dg.IsEnabled = true;
}

Using MVVM, how can I dynamically generate WPF DataGrid columns from a DataTable and also show button columns?

I am trying to dynamically generate a DataGrid from a DataTable while at the same time display two button columns for "Edit" and "Delete" functionality. I can manually create a button column in XAML, and I can dynamically generate columns from a DataTable, but I can't seem to do both. If I were just coding this in the code-behind I think it would be a much simpler problem to solve because I would have direct access to the control. However, I am building this View using MVVM and I can't think of a way to manipulate the View dynamically to this level of detail.
Here's a little bit of my code (please note that there may be some Copy/Paste FAILS that are obvious to someone with more WPF/MVVM experience than I have):
XAML:
<DataGrid x:Name="grdMapValues"
AutoGenerateColumns="True"
ItemsSource="{Binding GridSourceDataTable}">
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button HorizontalAlignment="Left"
Command="{Binding Path=DataContext.Commands.AddMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/new.gif" ToolTip="New" />
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Path=DataContext.Commands.EditMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/edit.gif" />
</Button>
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Path=DataContext.Commands.DeleteMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/delete.gif" />
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
private DataTable _gridSource;
public DataTable GridSourceDataTable
{
get
{
return _gridSource;
}
set
{
_gridSource = value;
OnPropertyChanged("GridSourceDataTable");
}
}
private void GenerateDataTable()
{
switch (caseInt)
{
case 1:
if (_isDate)
{
_gridSource.Columns.Add("Date", typeof(DateTime));
}
else
{
_gridSource.Columns.Add("Number", typeof(string));
}
_gridSource.Columns.Add("Value", typeof(decimal));
_gridSource.Columns.Add("SourceString", typeof(string));
GridIsVisible = true;
break;
//Other similar case blocks removed
default:
GridIsVisible = false;
break;
}
OnPropertyChanged("GridSourceDataTable");
}
So I am guessing that the solution, if there is one, will involve either some hybrid of XAML and the DataTable property OR it will involve a more sophisticated object or property than my simple DataTable.
I've been working on this one for several hours, so any help that can be offered will be greatly appreciated.
You can use attached behaviors to achieve what you are trying. You will be manipulating and adding additional columns to the grid from an attached behavior - that means you are still following MVVM pattern.
This is the attached behavior I tried:
public class DataGridColumnBehavior : Behavior<DataGrid>
{
public AdditionalColumnsList AdditionalColumns { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AddAdditionalColumns();
}
void AddAdditionalColumns()
{
if(AdditionalColumns == null || AdditionalColumns.Count == 0) return;
foreach (var additionalColumn in AdditionalColumns)
{
AssociatedObject.Columns.Add(new DataGridTemplateColumn
{
HeaderTemplate = additionalColumn.HeaderTemplate,
CellTemplate = additionalColumn.CellTemplate
});
}
}
}
I have used couple of simple classes to represent additional column and additional column list.
public class AdditionalColumn
{
public DataTemplate HeaderTemplate { get; set; }
public DataTemplate CellTemplate { get; set; }
}
public class AdditionalColumnsList : List<AdditionalColumn>
{
}
And, this is how you attach a behavior to DataGrid:
<DataGrid Margin="5" ItemsSource="{Binding GridSource}">
<i:Interaction.Behaviors>
<local:DataGridColumnBehavior>
<local:DataGridColumnBehavior.AdditionalColumns>
<local:AdditionalColumnsList>
<local:AdditionalColumn>
<local:AdditionalColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Button Header" />
</DataTemplate>
</local:AdditionalColumn.HeaderTemplate>
<local:AdditionalColumn.CellTemplate>
<DataTemplate>
<Button Content="Button" Command="{Binding ButtonCommand}" />
</DataTemplate>
</local:AdditionalColumn.CellTemplate>
</local:AdditionalColumn>
</local:AdditionalColumnsList>
</local:DataGridColumnBehavior.AdditionalColumns>
</local:DataGridColumnBehavior>
</i:Interaction.Behaviors>
</DataGrid>
And the resulting grid:
Read this CodeProject article on attached behaviors.
Please note, I have used Blend Behavior here. This blog post discusses differences between attached behaviors and blend behaviors.

WPF RadioButtons in details UserControl unchecked when navigating through parent ListView

---edited to include full code sample---
I know there have been a lot of issues with the .NET 3.5 RadioButton, but I believe this is a different issue that I have not found anything on in my searching. I've put together a very minimal code sample to demonstrate the problem I'm observing.
I have a ListView in my XAML that has its SelectedItem bound to the SelectedModel in my ViewModel. This same XAML contains a StackPanel that shows RadioButton options for the SelectedModel when navigating through items in the ListView. Everything works great until I add a RadioButton group into the mix. Now, if I select a record in the ListView that has one of the RadioButton options checked, then select another record in the ListView that has a different option checked, and now select the previous record in the ListView again, all of the RadioButtons for that ListView item will be unchecked. If I move up and down through the ListView long enough, every RadioButton, and the underlying Boolean values, will be set to false.
Model code:
public class Model
{
public string Name { get; set; }
public bool IsOne { get; set; }
public bool IsTwo { get; set; }
public bool IsThree { get; set; }
}
ViewModel code:
public class ViewModel: INotifyPropertyChanged
{
private Model _selectedModel;
public event PropertyChangedEventHandler PropertyChanged;
public List<Model> ModelList { get; set; }
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
}
}
public ViewModel()
{
ModelList = new List<Model>();
ModelList.Add(new Model() {Name = "Florida"});
ModelList.Add(new Model() {Name = "Texas"});
ModelList.Add(new Model() {Name = "Arizona"});
ModelList.Add(new Model() {Name = "Washington"});
}
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
And finally, the XAML code:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Path=ModelList}"
SelectedItem = "{Binding SelectedModel}"
DisplayMemberPath="Model">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}"
Width="100"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1">
<RadioButton x:Name="IsOne" Content="One" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsOne}"
GroupName="{Binding Path=SelectedModel.Name}"/>
<RadioButton x:Name="IsTwo" Content="Two" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsTwo}"
GroupName="{Binding Path=SelectedModel.Name}" />
<RadioButton x:Name="IsThree" Content="Three" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsThree}"
GroupName="{Binding Path=SelectedModel.Name}" />
</StackPanel>
</Grid>
Any ideas of what would be causing the binding to behave in the manner?
Thanks,
Glenn
If you have a number of RadioButtons in each row of your ListView, you will need to provide a unique GroupName for each set of RadioButtons in each row. The only way that I know how to do that is if you add an extra property to the data type class that you are displaying in the GridView. It doesn't matter what that value is as long as each item in the collection has a different value.
You can then Bind the new item property to the GroupName property of each RadioButton in the set:
<StackPanel>
<RadioButton Content="Demo" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Screener" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Vote" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Vote Follow Up" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Named Vote" GroupName="{Binding GroupName}" ... />
</StackPanel>
I found an article that demonstrates this nicely with XAML examples:
Grouped RadioButton for WPF Datagrid
UPDATE >>>
Yep, in your situation, when you have a group of RadioButtons in a container panel of some kind, you don't even need to supply a GroupName as the Framework does that for us 'under the covers'. I refactored your XAML to use a different Binding to see if that made any difference, but it didn't. You've found a bone fide bug there. Here is the XAML that I ended with that still exhibits the same problem:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding ModelList}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding SelectedModel}" DisplayMemberPath="Model">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1">
<RadioButton x:Name="IsOne" Content="One" Margin="3" IsChecked="{Binding ModelList/IsOne}" />
<RadioButton x:Name="IsTwo" Content="Two" Margin="3" IsChecked="{Binding ModelList/IsTwo}" />
<RadioButton x:Name="IsThree" Content="Three" Margin="3" IsChecked="{Binding ModelList/IsThree}" />
</StackPanel>
</Grid>
I removed the unused properties from your code example in your question. I would also suggest that you edit your question with a more accurate description of the problem... although I didn't quite work out the pattern of clicks that it would take to uncheck a CheckBox, I could see that it has nothing to do with the third RadioButton in particular. It certainly happened on the other two as well.
It might be worth reporting this as a bug on the Microsoft Connect website... perhaps there's an explanation or workaround there already?
I have confirmed that this is a known issue that has continued into Framework 4.5. Franklin Chen provided me with a nice little workaround that extends the RadioButton class.
Here is a link to his answer.
Here is his code. Now just bind to the IsCheckedReal property and everything works.
public class RadioButtonExtended : RadioButton
{
static bool m_bIsChanging = false;
public RadioButtonExtended()
{
this.Checked += new RoutedEventHandler(RadioButtonExtended_Checked);
this.Unchecked += new RoutedEventHandler(RadioButtonExtended_Unchecked);
}
void RadioButtonExtended_Unchecked(object sender, RoutedEventArgs e)
{
if (!m_bIsChanging)
this.IsCheckedReal = false;
}
void RadioButtonExtended_Checked(object sender, RoutedEventArgs e)
{
if (!m_bIsChanging)
this.IsCheckedReal = true;
}
public bool? IsCheckedReal
{
get { return (bool?)GetValue(IsCheckedRealProperty); }
set
{
SetValue(IsCheckedRealProperty, value);
}
}
// Using a DependencyProperty as the backing store for IsCheckedReal. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedRealProperty =
DependencyProperty.Register("IsCheckedReal", typeof(bool?), typeof(RadioButtonExtended),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedRealChanged));
public static void IsCheckedRealChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
m_bIsChanging = true;
((RadioButtonExtended)d).IsChecked = (bool)e.NewValue;
m_bIsChanging = false;
}
}

WPF DataGrid Sync Column Widths

I've got two WPF Toolkit DataGrids, I'd like so that when the user resizes the first column in the first grid, it resizes the first column in the second grid. I've tried binding the width of the DataGridColumn in the second grid to the appropriate column in the first grid, but it doesn't work. I'd prefer to use all xaml, but I'm fine with using code behind as well.
<tk:DataGrid Width="100" Height="100">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn x:Name="Column1" Width="50"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
<tk:DataGrid Width="100" Height="100">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn x:Name="Column1Copy" Width="{Binding Path=ActualWidth, ElementName=Column1}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
I also tried binding to Width instead of ActualWidth, but neither works.
Any help is greatly appreciated.
Well, I don't think that it is possible using straight XAML, but I still feel like it should because DataGridColumn does derive from DependencyObject. I did find a way to do it programatically though. I'm not thrilled about it, but it works:
DataGridColumn.WidthProperty.AddValueChanged(upperCol, delegate
{
if (changing) return;
changing = true;
mainCol.Width = upperCol.Width;
changing = false;
});
DataGridColumn.WidthProperty.AddValueChanged(mainCol, delegate
{
if (changing) return;
changing = true;
upperCol.Width = mainCol.Width;
changing = false;
});
public static void AddValueChanged(this DependencyProperty property, object sourceObject, EventHandler handler)
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(property, property.OwnerType);
dpd.AddValueChanged(sourceObject, handler);
}
You can use the DataGrid LayoutUpdated method to manipulate other objects regarding the column widths.
private void dataGrid1_LayoutUpdated(object sender, EventArgs e)
{
for(int i = 0 ; i < dataGrid1.Columns.Count && i < dataGrid2.Columns.Count ; ++i)
dataGrid2.Columns[i].Width = dataGrid1.Columns[i].ActualWidth;
}
I did a quick solution to this to using a attached behavior, Inspired by Ahmed answer above.
public class DataGridWidthSyncronizerBehavior
{
public static readonly DependencyProperty SyncronizeWidthWithProperty =
DependencyProperty.RegisterAttached("SyncronizeWidthWith",
typeof(DataGrid),
typeof(DataGridWidthSyncronizerBehavior),
new UIPropertyMetadata(null, SyncronizeWidthWithChanged));
public static void SetSyncronizeWidthWith(DependencyObject target, DataGrid value)
{
target.SetValue(SyncronizeWidthWithProperty, value);
}
public static DataGrid GetSyncronizeWidthWith(DependencyObject target)
{
return (DataGrid)target.GetValue(SyncronizeWidthWithProperty);
}
private static void SyncronizeWidthWithChanged(DependencyObject obj, DependencyPropertyChangedEventArgs dpargs)
{
if (!(obj is DataGrid sourceDataGrid))
return;
if (!(sourceDataGrid.GetValue(SyncronizeWidthWithProperty) is DataGrid targetDataGrid))
return;
void Handler(object sender, EventArgs e)
{
for (var i = 0; i < sourceDataGrid.Columns.Count && i < targetDataGrid.Columns.Count; ++i)
targetDataGrid.Columns[i].Width = sourceDataGrid.Columns[i].ActualWidth;
}
sourceDataGrid.LayoutUpdated -= Handler;
sourceDataGrid.LayoutUpdated += Handler;
}
}
XAML:
<DataGrid local:DataGridWidthSyncronizerBehavior.SyncronizeWidthWith="{Binding ElementName=SyncronizedHeaderGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Header 1"
Binding="{Binding Item1}" />
<DataGridTextColumn Header="Header 2"
Binding="{Binding Item2}"/>
<DataGridTextColumn Header="Header 3"
Binding="{Binding Item3}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="SyncronizedHeaderGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Header 1"
Binding="{Binding Item1}" />
<DataGridTextColumn Header="Header 2"
Binding="{Binding Item2}"/>
<DataGridTextColumn Header="Header 3"
Binding="{Binding Item3}"/>
</DataGrid.Columns>
</DataGrid>
The Second DataGrids, header and cell width, is now syncronized with the first grid header width.
I tried this:
<tk:DataGrid Width="100" Height="100" x:Name="Grid1" Grid.Column="0">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn x:Name="Column1" Width="50"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
<tk:DataGrid Width="100" Height="100" x:Name="Grid2" Grid.Column="1">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn x:Name="Column1Copy" Width="{Binding Mode=TwoWay, Path=Columns[0].ActualWidth, ElementName=Grid1}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
However, It looks like since DataGridColumns do not derive from FrameworkElement but instead derive from DependencyObject, binding in this manner is not available.
If you want to bind column Width property in XAML, in 2 DataGrid's than you have to do the following.
In the first DataGrid name the DataGridTextColumn:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn x:Name="Col1"/>
</DataGrid.Columns>
</DataGrid>
In the second DataGrid add a DiscreteObjectKeyFrame pointing to the above mentioned column as a resource, and use the following Binding to Width property on the DataGridTextColumn you want to "link":
<DataGrid>
<DataGrid.Resources>
<DiscreteObjectKeyFrame x:Key="proxyCol1" Value="{Binding ElementName=Col1}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Width="{Binding Path=Value.Width, Mode=TwoWay, Source={StaticResource proxyCol1}}"/>
</DataGrid.Columns>
</DataGrid>
I found a solution to this problem, and an extra cool solution :-)
You can download the WPF toolkit and get the code of the DataGrid.
Once you have the code all you have to do is change the DataGridColumn class to inherit FrameworkElement instead of DependencyObject.
Once you do so - you are left with only one problem, the DataContext of the column would not be initialized since the column is not part of the logical tree, adding it to the logical tree would solve this.
You can do it like this:
Where the OnColumnInitialization is:
private void OnColumnInitialization(object sender, EventArgs e)
{
AddLogicalChild(sender);
}
Now that it is part of the logical tree you have the same data context and you can use binding on the
Width property.
If all are bound to the same Width - you have a complete sync of the Width of your columns.
This worked for me :-)
Gili

Resources