I'm trying to work with a datagrid using the MVVM pattern. The problem is that whenever I change the VM property which is binded to SelectedItem to null, the View doesn't "deselect" the currently selected item. This is my binding in xaml:
<DataGrid Grid.Column="0" Grid.Row="0"
ItemsSource="{Binding Path=Users}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
The SelectedItem binding works from the view to the VM thus in the SelectedUser property I always have the selected object. The problem is that in the VM I'm doing some stuff which sometimes changes the SelectedUser property to null so I would expect the datagrid to deselect the row as well. Instead, it remains selected and if I try to click on the same row, the property doesn't update. If I click on any other row, the property changes as expected.
Is there a way to make the datagrid deselect if it's binded property is set to null? Also I'm looking for a MVVM solution as I don't want to write code behind. I can solve this by writing code behind so don't waste time offering such solutions :)
l.e.: this is my property in the VM:
public RPLUser SelectedUser
{
get
{
return selectedUser;
}
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
}
}
Thanks in advance!
I recommend to check the Output Window in visual studio and see if any Binding is failing.
Are you sure when you select something, the selection updates into the SelectedUser property?
Did u put a breakpoint in setter of SelectedUser and see that it is hitting when you select something on the datagrid?
The reasons for this Binding to break could be many ...
SelectedUser is of different type than individual Users.
SelectedUser does not match by reference with any items in Users.
How and where are you setting null?
The following code in my case works perfectly fine...
<tk:DataGrid MaxHeight="200" AutoGenerateColumns="False"
ItemsSource="{Binding}"
SelectedItem="{Binding MySelItem,
ElementName=MyDGSampleWindow,
Mode=TwoWay}"
IsReadOnly="True">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Key"
Binding="{Binding Key,
Mode=OneWay}"/>
<tk:DataGridTextColumn Header="Value"
Binding="{Binding Value,
Mode=OneWay}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
When I set MyDGSampleWindow.MySelItem as null, the datagrid propertly deselects. Perhaps you might need to give us more input on how are you actually setting the value as null.
Did you try setting IsSynchronizedWithCurrentItem="True" in the xaml properties for the DataGrid? AFAIK, this will allow you to unselect it by setting the SelectedUser to null.
I cannot test it at the moment, but you could also try to add this in the setter of your property:
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
ICollectionView collectionView = CollectionViewSource.GetDefaultView(Users);
collectionView.MoveCurrentTo(selectedUser);
}
(For ICollectionView to do anything, you will need to have IsSynchronizedWithCurrentItem set)
Like I said, I cannot test this right now. Also, the setter of the property is probably not the best place to put it. Maybe create an event handler for the PropertyChangedevent locally and put that logic there.
Let me know if it helps, else I'll see if I can run a short test...
Yeah may need to add the XAML UpdateSourceTrigger to update the UI.
SelectedItem="{Binding SomeProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
The DataGrid will not Deselect it automatically as DataGridRow's IsSelected property should be set to False.
You can do that by Setting a style on DataGrid.. like
<Style x:Key="dataGridRowStyle"
BasedOn="{StaticResource {x:Type WPFToolkit:DataGridRow}}"
TargetType="{x:Type WPFToolkit:DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
The IsSelected property should be the of the object i.e in your case RPLUser should have a property Isselected
Then before you set the SelectedUser to null... just do SelectedUser.IsSelected=False
And dont forget to attach this style to the DataGridRowStyle in Datagrid
I am using WPFToolkit you can modify the style if you are targetting .NET 4.0
Related
I am trying to get the bool value of the checkbox present in the Listview. I am binding to a bool public property "Assignm" in the view model. I tried the below binding pattern but the problem is if i select one checkbox it selects all checkboxes and vice versa. I think this is because relativesource is listview and it works on complete listview. I also tried changing the relative source to ListviewItem but that didn't trigger anything. Can someone help me please. Do i need to change something here ?
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding MU_Identifier}" IsChecked="{Binding DataContext.Assignm, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}">
</CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
Because your binding for IsChecked property is Assignm property which seems to be one property of your view model.
If there is a boolean property named Assignm for the data model of the DataSource of ListView, then just change the binding like this: {Binding Assignm}, as Tag property does.
All your items are bounded to a single property, so when one item changes a property in your context it changes on other items.
To provide correct work all your items from ItemsSource should have property IsChecked.
Check this Example
In the below window, the Existing Reports combo is bound to an observeablecollection of reportObjects. I have a reportObject property currentReport bound to the combo's SelectedValue property, OneWay. However, that's not working when bound in XAML.
SelectedValue="{Binding currentReport, Mode=OneWay}"
TwoWay binds fine, but I can't do it that way without writing an undo() method to the reportObject class. I'm binding the currentReport's properties to the various textboxes for editing. I want to bind OneWay so the source doesn't get changed. The currentReport's properties are all TwoWay bound to the corresponding textboxes so when I update the table in SQL [Save], it'll pull from that object, who's data is current.
<TextBox Text="{Binding currentReport.reportName, Mode=TwoWay}"
All of the properties bound from currentReport to the textboxes work fine as well. The only problem is the OneWay binding from the SelectedValue to the currentReport object. Does anyone have any ideas how to get this to work? I saw there was a bug, but the post I saw was 2009.
Sorry about the yellow. Not my idea. =)
EDIT: Added this XAML just in case.
<ComboBox ItemsSource="{Binding reportsCollection}" SelectionChanged="cboReports_SelectionChanged"
DisplayMemberPath="displayName"
SelectedValue="{Binding currentReport, Mode=TwoWay}"
x:Name="cboReports" Width="342" Height="40" VerticalAlignment="Center"/>
Forget about you need to change values - that is a separate problem - need to review your data design. Start with the UI problem question. If you want a user to be able to select an item from a combo box then it must have two way binding. Your first question is SelectedValue="{Binding currentReport, Mode=OneWay}" is failing why?
I created programatically a class (I called it ViewGrid) so that I use an instance of it as ItemTemplate for my ListBox control; of course, it's my data template for the listboxitem....
Also, in my ViewGrid class, I got a dependency property called IsChecked and I want to keep it in sync with the ListBoxItem's IsSelected property. I noticed that in SL there no relativesource-findancestor-ancestortype support for binding as in WPF, still, I need to find a way to keep my IsChecked property synchronized with the IsSelected property of the internally generated ListBoxItem for my ListBox control. Can you help?
Here is a ListBox defined in XAML that uses the IsSelected property of each LitBoxItem to show or hide a button when selected. You just need to duplicate that Binding approach for the ListBoxItems you create in code. Either that, or create a UserControl with the appropriate ListBoxItem XAML, and insert instances of those UserControls into your ListBox.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200" Height="120">
<StackPanel Margin="5">
<TextBlock Text="{Binding Name, Mode=OneWay}" />
<StackPanel Visibility="{Binding IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisible}}">
<Button Content="Show Details" Click="OnDetailsClick" Tag="{Binding}" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Good luck,
Jim McCurdy
Face To Face Software and YinYangMoney
UPDATE: I revisited this and found a much better solution. My original one remains below, but the way I actually ended up solving this problem is via using the ViewGrid in a ControlTemplate instead of a DataTemplate. Then you can use the RelativeSource TemplatedParent binding to bind to the IsSelected property of the ListBox. So, add the following to the Resources of the listbox or your page or user control:
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<ViewGrid IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
<!-- other controls may go here -->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ORIGINAL:
So after seven years, you almost certainly don't need an answer to this anymore... however, I recently spent a morning wrestling with this issue and thought I'd give my solution in case any similar unfortunate ends up here.
First off, anyone who's using Silverlight 5 is in luck as AncestorType is apparently now available for RelativeSource, letting you bind directly to the IsSelected property of the ListBoxItem. For those of us stuck with 4 or below, the only real workaround I came up with was "faking" the binding via use of events in the code behind.
To do this, assume you have your YourView XAML with a ListBox named "lbYourListBox" which has its ItemsSource and SelectedItem properties bound to appropriate properties on a YourViewModel class, along with a ViewGrid in its ItemTemplate whose IsChecked property is not bound to anything. Then, in your code behind file, you wire up events as follows:
public YourView()
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
((YourViewModel)this.DataContext).PropertyChanged += vm_PropertyChanged;
UpdateViewGrids();
};
}
// this part propagates changes from the view to the view model
private void viewGrid_Checked(object sender, RoutedEventArgs e)
{
var selectedVM = ((ViewGrid)sender).DataContext as SourceItemType;
((YourViewModel)this.DataContext).SelectedViewGridItem = selectedVM;
}
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "SelectedViewGridItem"))
{
UpdateViewGrids();
}
}
// this part propagates changes from the view model to the view
private void UpdateViewGrids()
{
var viewGrids = this.lbYourListBox.GetVisualDescendants().OfType<ViewGrid>();
var selectedVM = ((YourViewModel)this.DataContext).SelectedViewGridItem;
foreach (var grid in viewGrids)
{
grid.IsChecked = selectedVM == grid.DataContext;
}
}
The viewGrid_Checked event handler should be wired up to the Checked event of the view grid in the ItemTemplate. The GetVisualDescendants() method comes from the Silverlight Toolkit.
Important caveats:
The ViewGrid.Checked event should not fire except for the unchecked->checked transition, and no more than one view grid should be able to be selected at once. If those two things aren't true, you'll have to make appropriate edits to ensure this code can't cause an infinite event-driven loop. (Of course, if you don't need two-way binding, you only need one of these event handlers and event ping-pong isn't a concern.)
I wrote this for a user control which had its data context set in XAML, which is why the event handler for the view model's PropertyChanged event is only assigned after the view is loaded. Depending on how and when your view and view model are bound to each other, you may have to assign that earlier/later/differently.
This won't work if the view grids aren't visible, GetVisualDescendants seems to ignore hidden/collapsed controls.
I've been going round in circles with this for a couple of days, and I'm hoping a WPF guru can see where I'm going wrong.
I'm setting CurrentViewModel in code. The Selected item of my ListBox and the Content of my ContentControl bind correctly. But when changing the selected item in the Listbox via the UI the CurrentViewModel is being set but the Content Control is not being updated.
I'm using a data template to map my Views and View Models.
<DataTemplate DataType="{x:Type ViewModel:MyViewModel}">
<View:MyView />
</DataTemplate>
I have a ListBox which is bound to an observable collection of ViewModels. The Selected Item is bound to the current view model.
<ListBox ItemsSource="{Binding MyViewModelCollection}" DisplayMemberPath="DisplayName" SelectedItem="{Binding CurrentViewModel, Mode=TwoWay}"/>
I also have a content control that is also bound to the CurrentView Model
<ContentControl Content="{Binding CurrentViewModel, Mode=TwoWay}"/>
This is the property that they are both bound to
public MyViewModel CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel== value) return;
_currentViewModel= value;
OnPropertyChanged("CurrentViewModel");
}
}
I've edited the names for clarity and removed formatting information.
Any help greatly appreciated.
Cheers,
Daniel
EDIT: Came across the link How can I debug WPF bindings?. I set a break point on the Content binding and it does indeed only get called once when the binding is first set.
You should not be setting TwoWay as the mode on your ContentControl:
<ContentControl Content="{Binding CurrentViewModel, Mode=OneWay}"/>
This is because you intend your ContentControl to read the value, but never write it.
As an aside, you can also bind the ContentControl to the currently selected item in the collection, rather than to that property by doing this:
<ListBox ItemsSource="{Binding MyViewModelCollection}"
DisplayMemberPath="DisplayName"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl Content="{Binding MyViewModelCollection/}"/>
The "slash" (/) at the end of the collection indicates the current item selected in the collection and setting that current item property is as simple as setting the IsSynchronizedWithCurrentItem equal to true.
A lot of times I find with this combination, I really don't need the extra property on my view model.
Anyway, I hope this helps.
I'm trying to make an Address control that has an IsReadOnly property, which will make every TextBox inside read only when set to true.
<my:AddressControl Grid.Column="1" Margin="5" IsReadOnly="True"/>
I've managed to do this just fine with a dependency property and it works.
Here's a simple class with the dependency property declared :
public partial class AddressControl : UserControl
{
public AddressControl()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool),
typeof(AddressControl), null);
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
}
In the XAML for this codebehind file I have a Textbox for each address line:
<TextBox IsReadOnly="{Binding IsReadOnly}" Text="{Binding City, Mode=TwoWay}"/>
<TextBox IsReadOnly="{Binding IsReadOnly}" Text="{Binding State, Mode=TwoWay}"/>
<TextBox IsReadOnly="{Binding IsReadOnly}" Text="{Binding Zip, Mode=TwoWay}"/>
Like i said this works just fine.
The problem is that the Address control itself is bound to its parent object (I have several addresses I am binding).
<my:AddressControl DataContext="{Binding ShippingAddress, Mode=TwoWay}" IsReadOnly="True">
<my:AddressControl DataContext="{Binding BillingAddress, Mode=TwoWay}" IsReadOnly="True">
The problem is that as soon as I set DataContext to something other than 'this' then the binding for IsReadOnly breaks. Not surprising because its looking for IsReadOnly on the Address data entity and it doesn't exist or belong there.
I've tried just about every combination of binding attributes to get IsReadOnly to bind to the AddressControl obejct but can't get it working.
I've tried things like this, but I can't get IsReadOnly to bind independently to the AddressControl property instead of its DataContext.
<TextBox IsReadOnly="{Binding RelativeSource={RelativeSource Self}, Path=IsReadOnlyProperty}" Text="{Binding City, Mode=TwoWay}" />
I think I'm pretty close. What am I doing wrong?
With this answer (actually my own answer to a similar question) I have a good [better] solution.
I still have to iterate through the textboxes, but I don't have to set the actual value. I can create bindings in the codebehind - just not with XAML.
I think you're stuck, at least, if you want to do this just via binding. My guess is that you're going to have to resort to code-behind, presumably by iterating through your child textbox controls and setting their IsReadOnly propert as a side-effect of your Address control's IsReadOnly property.
Unlike some folks who think that any code sitting in a code-behind file is effectively an admission of failure, I don't get religious about it: if throwing some code into a code-behind is the easiest way to do something, that's where I put my code. On the contrary, if I have to spend half a day trying to figure out how to do something via binding that I could do in five minutes with a code-behind, that's failure, IMO.