Scroll the scrollviewer to top through viewmodel - wpf

I am using the ScrollViewer with the MVVM pattern, and a list of items is wrapped by the ScrollViewer, such as
<ScrollViewer>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
The items of the listview are bound to a collection of objects in the viewmodel. I want the scrollviewer to scroll to the top whenever a item is added or removed from the collection.
I need the viewmodel to trigger the event, rather than using the ScrollToTop() method in the code-behind of the view.

IMHO, the clearest way to do this is using a "Behavior" via an AttachedProperty. An AttachedProperty is a mechanism to extend existing controls functionality.
First, create a class to hold the AtachedProperty, for instance:
public class ScrollViewerBehavior
{
public static bool GetAutoScrollToTop(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToTopProperty);
}
public static void SetAutoScrollToTop(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToTopProperty, value);
}
public static readonly DependencyProperty AutoScrollToTopProperty =
DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
{
var scrollViewer = o as ScrollViewer;
if (scrollViewer == null)
{
return;
}
if ((bool)e.NewValue)
{
scrollViewer.ScrollToTop();
SetAutoScrollToTop(o, false);
}
}));
}
This attached property allows a ScrollViewer having "magically" a new property of type Boolean, acting like a DependencyProperty in your XAML. If you bind this property to a standard property in your ViewModel, for instance:
private bool _reset;
public bool Reset
{
get { return _reset; }
set
{
_reset = value;
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
}
}
(again, the name is up to you) and then you set this Reset property to true, your ScrollViewer will scroll to top.
I have named the AtachedPropertyas AutoScrollToTop, but the name is not important for this purpose.
The XAML will be something like:
<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
Note: my is the namespace where your ScrollViewerBehavior class lives. For example: xmlns:my="clr-namespace:MyApp.Behaviors"
Finally, the only thing you have to do in your ViewModel is to set Reset = true when you like, in your case, when you add or remove an element from the collection.

Create a new ListView control which extend Listview and use this new one instead
public class ScrollListView : ListView
{
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems.Count > 0)
this.ScrollIntoView(e.OldItems[e.OldStartingIndex]);
base.OnItemsChanged(e);
}
}

I have also faced a similar scenario where I needed to assign ScrollViewer's HorizontalOffset and VerticalOffset programmatically. I am afraid there is no direct binding mechanism for this. What I did was a way around (believe me, I still do not like the approach I followed, but I did not find any other option). Here is what I suggest:
Hook the ScrollViewer's Loaded event, cast the sender object to ScrollViewer and assign it to a property in DataContext (Means you need to keep a ScrollViewer propery in DataContext which will hold the reference of ScrollViewer in the UI). Hook up ObservableCollection's CollectionChanged events in ViewModel and using the ScrollViewer property, you can call methods like ScrollToTop() etc.
This is just a way around. I am still looking for better solution.

The simplest correct way to do this in MVVM is by creating an event in your viewmodel and subscribing to it from your view. And then, in the event handler, call ScrollToTop.
You fire the event from your viewmodel every time your collection is modified, for instance, and then it's up to the view to react to that event and scroll the list to the top.
Even if this involves some code-behind and demands that the view knows part of its viewmodel, it doesn't violate the MVVM pattern, unlike other workarounds.
public interface IMyViewModel
{
event EventHandler MyCollectionChanged;
}
public class MyViewModel : IMyViewModel
{
public event EventHandler MyCollectionChanged;
// More viewmodel related stuff
protected virtual void OnMyCollectionChanged(EventArgs e)
{
if (MyCollectionChanged != null)
MyCollectionChanged(this, e);
}
}
public class MyWindow : Window
{
public MyWindow(IMyViewModel viewModel)
{
this.DataContext = viewModel;
InitializeComponent();
(this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
}
private void MyCollectionChangedEventHandler(object sender, EventArgs e)
{
// Do view related stuff
scrollViewer.ScrollToTop();
}
}
EDIT: But it can be refined a lot more, of course. If you want to avoid using code-behind, look for DataEventTriggers. If you don't mind about code-behind but are concerned about memory leaks, look for weak events.
And finally, since the logic you want is 100% view-related (have the ListView scroll every time an item is added or removed to it), you could also implement it as a Behavior / attached property, or extending the ListView. That could get a tad more convoluted, but I encourage you to give those options some thought.

Related

ListView with checkboxes

I have a list of items displayed as a ListBox.
<ListView ItemsSource="{Binding ListOfSomeItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsReceived}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Now there is 2 user cases I need to implement:
1) When user mark an Item as received (the CheckBox get checked) I need to update the item. How should I bind the checked event to ICommand in my ViewModel?
2) When user tries to remove received flag (un-checks the checkBox) a pop up should be provided with an option to cancel the operation (if someone clicked the checkBox by accident) or to provide a note for a reason. That note along with the Item that been unchecked should be sent to different ICommand in VM.
Any suggestions?
Thanks in advance
UPDATE:
My viewModel do implements INoftiyPropertyChanged but I do not have property for the single item. The property is nested in complex class: something like Account.Holders[x].Requirements[y].IsReceived.
Why just not bind a command which should be exposed by ViewModel:
<CheckBox
Command="{Binding ReceivedStatusChangedCommand}"
CommandParameter="{Binding ...IsReceived}"
/>
Then in command handler you can analyze passed in value of the IsReceived as command parameter.
Since you're binding to an IsChecked property in your object, you can simply listen to the PropertyChanged event for that object and call whatever method you need when it changes.
For example,
// Wireup CollectionChanged in Constructor
public MyVMConstructor()
{
ListOfSomeItems = new List<SomeItem>();
ListOfSomeItems.CollectionChanged += ListOfSomeItems_CollectionChanged;
}
// In CollectionChanged event, wire up PropertyChanged event on items
void ListOfSomeItems_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(SomeItem item in e.NewItems)
item.PropertyChanged += SomeItem_PropertyChanged;
}
if (e.OldItems != null)
{
foreach(SomeItem item in e.OldItems)
item.PropertyChanged -= SomeItem_PropertyChanged;
}
}
// In PropertyChanged, if property was IsReceived then verify and update
void SomeItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsReceived")
{
if (MessageBox.Show("Are you sure?", "Confirm", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
var item = sender as SomeItem;
UpdateSomeItem(item.Id, item.IsReceived);
}
}
}
Your ViewModel should implement INoftiyPropertyChanged. Just listen to the PropertyChanged event and respond to it.
Edit
Based on the structure of your ViewModel, if Account needs to respond the a Requirement, it might be best to use something like Prism's EventAggregator or MVVM Light's Messenger.
MVVM solution would be:
Since bindings are two way you don't need to do anything to have your object updated from UI. But if you want your UI to be updated on changes in model, your model classes need to implement INotifyPropertyChanged
I imagine this would be done in IsReceived setter. Add two fields to your model class.
public string ValidationError
{ get; set; }
public bool HasValidationError
{ get; set; }
Then create a PopUp with error which is hidden by default. Implement INotifyPropertyChanged in your class, and bind PopUp visibility to HasValidationError, and message in inner TextBlock to ValidationError.

How to have a button in a datagrid template that will remove the item when clicked

I would like to use a datatemplate for my datagrid columns and have a button for each item. I would like the item to be removed if the user clicks the button. I am using the MVVM pattern. How would I accomplish this?
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="50" Content="Remove" Command="{Binding RemoveItemCommand}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
public class ItemViewModel
{
public ItemViewModel()
{
RemoveCommand = new MyCommand(Remove);
}
public event EventHandler ItemRemoved;
public ICommand RemoveCommand { get; private set; }
private void Remove()
{
// Whatever it takes to remove item from your data store
service.Remove(this.Data);
var removeItem = ItemRemoved;
if (removeItem != null)
removeItem(this, EventArgs.Empty);
}
}
public class ListViewModel
{
public ListViewModel(IEnumerable<ItemViewModel> items)
{
ItemVMs=new ObservableCollection<ItemViewModel>(items);
foreach (var item in ItemVMs)
item.ItemRemoved += RemoveSelectedItem;
}
public ObservableCollection<ItemViewModel> ItemVMs { get; private set; }
private void RemoveSelectedItem(object sender, EventArgs e)
{
var item = sender as ItemViewModel;
item.ItemRemoved -= RemoveSelectedItem;
ItemVMs.Remove(item);
}
}
Each item's RemoveCommand would be bound to its button in your DataGrid. It sounds like you already have that part done. Make the ListViewModel's ItemVMs property the data source for your DataGrid.
The View is responsible for this. You can simply use codebehind to control the visibility of UI elements in response to user actions in the UI.
Sometimes, it is better to be practical than be rigidly dogmatic.
Well, now that you have edited your question, it becomes a completely different matter.
Your DataGrid should be bound to a collection of items.
Your button should be bound to a command on the ViewModel, and the CommandParameter should be the Model that particular row is bound to.
<DataTemplate>
<Button Content="Remove"
Command="{Binding DataContext.RemoveItemCommand,
ElementName=theWindow}"
CommandParameter="{Binding}" />
</DataTemplate>
Note some important things here. We need, from within the template, to bind to an ICommand on the ViewModel. The ViewModel is the DataContext of the Window. In this example, the window is named 'theWindow' (x:Name="theWindow"). Since the source of the Binding is the window, the Path must point to the ViewModel in the DataContext property on that Window.
We pass the current Model the DataGrid row is bound to into the command. This way, it is triival to remove it from the collection in the ViewModel.
public ObservableCollection<Model> Items {get;set;}
public ICommand RemoveItemCommand {get;set;}
// this method is called when RemoveItemCommand.Execute is called!
public void Execute(object parameter)
{
Items.Remove(parameter as Model);
}
This assumes you're using one of the standard delegated ICommand implementations out there. You can see how this is trivial to implement, and since the collection is an observable one, once you click the button and the Model is removed, the DataGrid will be notified of the change in the collection and remove that row.
You're probably better off using the standard routed events on the Click event of the button instead of a Command. The click event will allow you to retrieve the information about what control was clicked, and then you can also easily retrieve the parent of the button, to delete that item.

WPF ComboBox SelectedItem Set to Null on TabControl Switch

I've got a simple problem in my WPF application which has me banging my head on the table. I have a TabControl, where every TabItem is a View generated for a ViewModel using a DataTemplate similar to this:
<DataTemplate DataType="{x:Type vm:FooViewModel}">
<vw:FooView/>
</DataTemplate>
FooView contains a ComboBox:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar}"/>
and FooViewModel contains a simple Property: public Bar SelectedBar { get; set; }. My problem is that when I set the value for my ComboBox, change to another tab, then change back, the ComboBox is empty again. If I set a breakpoint on the setter for my property, I see that the property is assigned to null when I switch to another tab.
From what I understand, when a tab is switched, it is removed from the VisualTree - but why is it setting my ViewModel's property to null? This is making it very difficult for me to hold persistent state, and checking value != null does not seem like the right solution. Can anyone shed some like on this situation?
Edit: The call stack at the setter breakpoint only shows [External Code] - no hints there.
We just ran into the same problem. We found a blog entry describing the problem. It looks like it is a BUG in WPF and there is a workaround:
Specify the SelectedItem binding before the ItemsSource binding and the problem should be gone.
blog article
TLDR;
Change:
<ComboBox ItemsSource="{Binding Countries, Mode=OneWay}"
SelectedItem="{Binding SelectedCountry}"
DisplayMemberPath="Name" >
</ComboBox>
To:
<ComboBox SelectedItem="{Binding SelectedCountry}"
ItemsSource="{Binding Countries, Mode=OneWay}"
DisplayMemberPath="Name" >
</ComboBox>
My app is using avalondock & prims and had that exact problem. I has same thought with BSG, when we switched tab or document content in MVVM app, the controls as listview+box, combobox is removed from VisualTree. I bugged and saw most data of them was reset to null such as itemssource, selecteditem, .. but selectedboxitem was still hold current value.
A approach is in model, check its value is null then return like this:
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (_selectedEmployee == value ||
IsAdding ||
(value == null && Employees.Count > 0))
{
return;
}
_selectedEmployee = value;
OnPropertyChanged(() => SelectedEmployee);
}
But this approach can only solve quite good in first binding level. i mean,
how we go if want to bind SelectedEmployee.Office to combobox, do same is not good
if check in propertyChanged event of SelectedEmployee model.
Basically, we dont want its value is reset null, keep its pre-value. I found a new solution
consistently. By using attached property, i created KeepSelection a-Pro, bool type, for Selector controls, thus supply all its inherited suck as listview, combobox...
public class SelectorBehavior
{
public static bool GetKeepSelection(DependencyObject obj)
{
return (bool)obj.GetValue(KeepSelectionProperty);
}
public static void SetKeepSelection(DependencyObject obj, bool value)
{
obj.SetValue(KeepSelectionProperty, value);
}
// Using a DependencyProperty as the backing store for KeepSelection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepSelectionProperty =
DependencyProperty.RegisterAttached("KeepSelection", typeof(bool), typeof(SelectorBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(onKeepSelectionChanged)));
static void onKeepSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
var value = (bool)e.NewValue;
if (value)
{
selector.SelectionChanged += selector_SelectionChanged;
}
else
{
selector.SelectionChanged -= selector_SelectionChanged;
}
}
static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as Selector;
if (e.RemovedItems.Count > 0)
{
var deselectedItem = e.RemovedItems[0];
if (selector.SelectedItem == null)
{
selector.SelectedItem = deselectedItem;
e.Handled = true;
}
}
}
}
Final, i use this approach simply in xaml:
<ComboBox lsControl:SelectorBehavior.KeepSelection="true"
ItemsSource="{Binding Offices}"
SelectedItem="{Binding SelectedEmployee.Office}"
SelectedValuePath="Id"
DisplayMemberPath="Name"></ComboBox>
But, selecteditem will never null if selector's itemssource has items. It may affect
some special context.
Hope that helps.
Happy conding! :D
longsam
I had this same problem when scrolling through a virtualizing DataGrid that contains ComboBoxes. Using IsSynchronizedWithCurrentItem did not work, nor did changing the order of the SelectedItem and ItemsSource bindings. But here is an ugly hack that seems to work:
First, give your ComboBox an x:Name. This should be in the XAML for a control with a single ComboBox. For example:
<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}">
Then add these two event handlers in your codebehind:
using System.Windows.Controls;
using System.Windows;
namespace SATS.FileParsing.UserLogic
{
public partial class VariableTargetSelector : UserControl
{
public VariableTargetSelector()
{
InitializeComponent();
mComboBox.DataContextChanged += mComboBox_DataContextChanged;
mComboBox.SelectionChanged += mComboBox_SelectionChanged;
}
/// <summary>
/// Without this, if you grab the scrollbar and frantically scroll around, some ComboBoxes get their SelectedItem set to null.
/// Don't ask me why.
/// </summary>
void mComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget();
}
/// <summary>
/// Without this, picking a new item in the dropdown does not update IVariablePair.SelectedTarget.WritableData.
/// Don't ask me why.
/// </summary>
void mComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateSource();
}
}
}
Generally, I use SelectedValue instead of SelectedItem. If I need the object associated with the SelectedValue then I add a lookup field containing this to the target object (as I use T4 templates to gen my viewmodels this tends to be in a partial class). If you use a nullable property to store the SelectedValue then you will experience the problem described above, however if binding the SelectedValue to a non-nullable value (such as an int) then the WPF binding engine will discard the null value as being inappropriate for the target.
Edit:
Below stuff works (I hope...); I developed it because I followed the SelectedItems route described on the MVVM Lite page. However - why do I want to rely on SelectedItems? Adding an IsSelected property to my Items (as shown here) automatically preserves selected items (short of the mentioned cavet in above link). In the end, much easier!
Inital Post:
ok - that was a piece of work; I've a multi-column ListView with SelectionMode="Extension", which makes the whole thing fairly complex. My starting point is invoking tabItems from workspaces similar as describe here.
I made sure that in my ViewModel, I know when a tab item (workspace) is active. (This is a bit similar to here) - of course, somebody needs initalize SelectedWorkspace first.
private Int32 _selectedWorkspace;
public Int32 SelectedWorkspace {
get { return _selectedWorkspace; }
set {
_selectedWorkspace = value;
base.OnPropertyChanged("SelectedWorkspace");
}
}
protected Int32 _thisWorkspaceIdx = -1;
protected Int32 _oldSelectedWorkspace = -1;
public void OnSelectedWorkspaceChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "SelectedWorkspace") {
if (_oldSelectedWorkspace >= 0) {
Workspaces[_oldSelectedWorkpace].OnIsActivatedChanged(false);
}
Workspaces[SelectedWorkspace].OnIsActivatedChanged(true);
_oldSelectedWorkspace = SelectedWorkspace;
}
}
protected bool _isActive = false;
protected virtual void OnIsActivatedChanged(bool isActive) {
_isActive = isActive;
}
This allowed me to update the ViewModel selected items only if the tab item (workspace) was actually active. Hence, my ViewModel selected items list is preserved even as the tab item clears the ListView.SelectedItems. In the ViewModel:
if (_isActive) {
// ... update ViewModel selected items, referred below as vm.selectedItems
}
Last, when the tabItem got re-enabled, I hooked up to the 'Loaded' event and restored the SelectedItems. This is done in the code-behind of the View. (Note that whilst my ListView has multiple columns, one serves as a key, the others are for information only. the ViewModel selectedItems list only keeps the key. Else, the comparison below would be more complex):
private void myList_Loaded(object sender, RoutedEventArgs e) {
myViewModel vm = DataContext as myViewModel;
if (vm.selectedItems.Count > 0) {
foreach (string myKey in vm.selectedItems) {
foreach (var item in myList.Items) {
MyViewModel.MyItem i = item as MyViewModel.MyItem;
if (i.Key == myKey) {
myList.SelectedItems.Add(item);
}
}
}
}
}
if you suing async selection in WPF then remove it IsSynchronizedWithCurrentItem="True" from for the ComboBox, please refer to the document about IsSynchronizedWithCurrentItem:
<ComboBox
Name="tmpName"
Grid.Row="10"
Width="250"
Text="Best Match Position List"
HorizontalAlignment="Left"
Margin="14,0,0,0"
SelectedItem="{Binding Path=selectedSurceList,Mode=TwoWay}"
ItemsSource="{Binding Path=abcList}"
DisplayMemberPath="Name"
SelectedValuePath="Code"
IsEnabled="{Binding ElementName=UserBestMatchYesRadioBtn,Path=IsChecked}">
</ComboBox>
also takecare the binding
first use SelectedItem
then ItemsSource
ref:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/fb8a8ad2-83c1-43df-b3c9-61353979d3d7/comboboxselectedvalue-is-lost-when-itemssource-is-updated?forum=wpf
http://social.msdn.microsoft.com/Forums/en-US/c9e62ad7-926e-4612-8b0c-cc75fbd160fd/bug-in-wpf-combobox-data-binding
I solve my problem using the above
I once had a similar problem. It seems that the combobox looses the selected item in VisibilityChanged event. Workarround is to clear the binding before this occurs, and reset it when coming back. You can also try to set the Binding to Mode=TwoWay
Hope that this helps
Jan
I had the same problem and solved it with the following method attached to the Combobox DataContextChanged-Event:
private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
So everytime you want to remove the datacontext from the combobox, the old datacontext will be set again.
Everytime you change the active Tab of your TabControl, the Combobox will removed from your VisualTree and added if you go back to the one with your combobox. If the combo box is removed from the VisualTree, also the DataContext is set to null.
Or you use a class, which have implemented such feature:
public class MyCombobox : ComboBox
{
public MyCombobox()
{
this.DataContextChanged += MyCombobox_DataContextChanged;
}
void MyCombobox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
public void SetDataContextExplicit(object dataContext)
{
lock(this.DataContext)
{
this.DataContextChanged -= MyCombobox_DataContextChanged;
this.DataContext = dataContext;
this.DataContextChanged += MyCombobox_DataContextChanged;
}
}
}
I think the problem may be that you arent telling the Combo box when to bind back to the source. Try this:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar, UpdateSourceTrigger=PropertyChanged}"/
You can use the MVVM framework Catel and the catel:TabControl element there this problem is already solved.
Just don't allow your ViewModel's property to be changed if value becomes null.
public Bar SelectedBar
{
get { return barSelected; }
set { if (value != null) SetProperty(ref barSelected, value); }
}
That's it.

WPF DataGrid multiselect binding

I have a datagrid that is multi-select enabled. I need to change the selection in the viewmodel. However, the SelectedItems property is read only and can't be directly bound to a property in the viewmodel. So how do I signal to the view that the selection has changed?
Andy is correct. DataGridRow.IsSelected is a Dependency Property that can be databound to control selection from the ViewModel. The following sample code demonstrates this:
<Window x:Class="DataGridMultiSelectSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<StackPanel>
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" EnableRowVirtualization="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Value" Binding="{Binding Value}" />
</tk:DataGrid.Columns>
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</tk:DataGrid.RowStyle>
</tk:DataGrid>
<Button Content="Select Even" Click="Even_Click" />
<Button Content="Select Odd" Click="Odd_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Windows;
namespace DataGridMultiSelectSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new[]
{
new MyViewModel {Value = "Able"},
new MyViewModel {Value = "Baker"},
new MyViewModel {Value = "Charlie"},
new MyViewModel {Value = "Dog"},
new MyViewModel {Value = "Fox"},
};
}
private void Even_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[]) DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i%2 == 0;
}
private void Odd_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[])DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i % 2 == 1;
}
}
public class MyViewModel : INotifyPropertyChanged
{
public string Value { get; set; }
private bool mIsSelected;
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected == value) return;
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Be sure to set EnableRowVirtualisation="False" on the DataGrid element, else there's a risk that the IsSelected bindings fall out of kilter.
I haven't worked with the DataGrid much, but one technique that works for the ListView is to bind to the IsSelected property of the individual ListViewItem. Just set this to true for each object in your list, and then it will get selected.
Maybe the object that represents a row in the DataGrid also has an IsSelected property, and can be used in this way as well?
Guys, thanks for the help. My problem was solved. I think the problem is pretty common for new WPF developers, so I will restate my problem and as well as the solution in more details here just in case someone else runs into the same kind of problems.
The problem: I have a multi-select enabled datagrid of audio files. The grid has multiple column headers. The user can multi-select several row. When he clicks the Play button, the audio files will be played in the order of one the columns headers (say column A). When playback starts, the multi-select is cleared and only the currently playing file is highlighted. When playback is finished for all files, the multi-selection will be re-displayed. The playback is done in the viewmodel. As you can see, there are two problems here: 1) how to select the currently playing file from the viewmodel, and 2) how to signal to the view from the viewmodel that playback is finished and re-display the multi-selection.
The solution: To solve the first problem, I created a property in the viewmodel that is bound to the view's SelectedIndex property to select the currently playing file. To solve the second problem, I created a boolean property in the view model to indicate playback is finished. In the view's code behind, I subscribed the the boolean property's PropertyChanged event. In the event handler, the view's SelectedItems property is re-created from the saved multi-selection (the contents of SelectedItems was saved into a list and SelectedItems was cleared when playback started). At first, I had trouble re-creating SelectedItems. It turned out the problem was due to the fact that re-creation was initiated through a second thread. WPF does not allow that. The solution to this is to use the Dispatcher.Invoke() to let the main thread do the work. This may be a very simple problem for experienced developers, but for newbies, it's a small challenge. Anyway, a lot of help from different people.
Just use SelectedItems on any MultiSelector derived class , and use methods Add, Remove, Clear on IList it returns .

How to bind DataGridColumn.Visibility?

I have an issue similar to the following post:
Silverlight DataGridTextColumn Binding Visibility
I need to have a Column within a Silverlight DataGrid be visibile/collapsed based on a value within a ViewModel. To accomplish this I am attempting to Bind the Visibility property to a ViewModel. However I soon discovered that the Visibility property is not a DependencyProperty, therefore it cannot be bound.
To solve this, I attempted to subclass my own DataGridTextColumn. With this new class, I have created a DependencyProperty, which ultimately pushes the changes to the DataGridTextColumn.Visibility property. This works well, if I don't databind. The moment I databind to my new property, it fails, with a AG_E_PARSER_BAD_PROPERTY_VALUE exception.
public class MyDataGridTextColumn : DataGridTextColumn
{
#region public Visibility MyVisibility
public static readonly DependencyProperty MyVisibilityProperty =
DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(MyDataGridTextColumn), new PropertyMetadata(Visibility.Visible, OnMyVisibilityPropertyChanged));
private static void OnMyVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var #this = d as MyDataGridTextColumn;
if (#this != null)
{
#this.OnMyVisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
}
}
private void OnMyVisibilityChanged(Visibility oldValue, Visibility newValue)
{
Visibility = newValue;
}
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
set { SetValue(MyVisibilityProperty, value); }
}
#endregion public Visibility MyVisibility
}
Here is a small snippet of the XAML.
<DataGrid ....>
<DataGrid.Columns>
<MyDataGridTextColumn Header="User Name"
Foreground="#FFFFFFFF"
Binding="{Binding User.UserName}"
MinWidth="150"
CanUserSort="True"
CanUserResize="False"
CanUserReorder="True"
MyVisibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ShouldShowUser}"/>
<DataGridTextColumn .../>
</DataGrid.Columns>
</DataGrid>
A couple important facts.
The Converter is indeed defined above in the local resources.
The Converter is correct, it is used many other places in the solution.
If I replace the {Binding} syntax for the MyVisibility property with "Collapsed" the Column does in fact disappear.
If I create a new DependencyProperty (i.e. string Foo), and bind to it I receive the AG_E_PARSER_BAD_PROPERTY_VALUE exception too.
Does anybody have any ideas as to why this isn't working?
Here's the solution I've come up with using a little hack.
First, you need to inherit from DataGrid.
public class DataGridEx : DataGrid
{
public IEnumerable<string> HiddenColumns
{
get { return (IEnumerable<string>)GetValue(HiddenColumnsProperty); }
set { SetValue(HiddenColumnsProperty, value); }
}
public static readonly DependencyProperty HiddenColumnsProperty =
DependencyProperty.Register ("HiddenColumns",
typeof (IEnumerable<string>),
typeof (DataGridEx),
new PropertyMetadata (HiddenColumnsChanged));
private static void HiddenColumnsChanged(object sender,
DependencyPropertyChangedEventArgs args)
{
var dg = sender as DataGrid;
if (dg==null || args.NewValue == args.OldValue)
return;
var hiddenColumns = (IEnumerable<string>)args.NewValue;
foreach (var column in dg.Columns)
{
if (hiddenColumns.Contains ((string)column.GetValue (NameProperty)))
column.Visibility = Visibility.Collapsed;
else
column.Visibility = Visibility.Visible;
}
}
}
The DataGridEx class adds a new DP for hiding columns based on the x:Name of a DataGridColumn and its descendants.
To use in your XAML:
<my:DataGridEx x:Name="uiData"
DataContext="{Binding SomeDataContextFromTheVM}"
ItemsSource="{Binding Whatever}"
HiddenColumns="{Binding HiddenColumns}">
<sdk:DataGridTextColumn x:Name="uiDataCountOfItems">
Header="Count"
Binding={Binding CountOfItems}"
</sdk:DataGridTextColumn>
</my:DataGridEx>
You need to add these to your ViewModel or whatever data context you use.
private IEnumerable<string> _hiddenColumns;
public IEnumerable<string> HiddenColumns
{
get { return _hiddenColumns; }
private set
{
if (value == _hiddenColumns)
return;
_hiddenColumns = value;
PropertyChanged (this, new PropertyChangedEventArgs("HiddenColumns"));
}
}
public void SomeWhereInYourCode ()
{
HiddenColumns = new List<string> {"uiDataCountOfItems"};
}
To unhide, you only need to remove the corresponding name from the list or recreate it without the unhidden name.
I have another solution to this problem that uses an approach similar to the "Binding" property that you find on DataGridTextColumn. Since the column classes are DependencyObjects, you can't directly databind to them, BUT if you add a reference to a FrameworkElement that implements INotifyPropertyChanged you can pass a databinding through to the element, and then use a dependency property to notify the Column that the databinding has changed.
One thing to note is that having the binding on the Column itself instead of the Grid will probably mean that you will want to use a DataContextProxy to get access to the field that you want to bind the Visibility to (the column binding will default to the scope of the ItemSource).
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace XYZ.Controls
{
public class ExtendedDataGridTextColumn : DataGridTextColumn
{
private readonly Notifier _e;
private Binding _visibilityBinding;
public Binding VisibilityBinding
{
get { return _visibilityBinding; }
set
{
_visibilityBinding = value;
_e.SetBinding(Notifier.MyVisibilityProperty, _visibilityBinding);
}
}
public ExtendedDataGridTextColumn()
{
_e = new Notifier();
_e.PropertyChanged += ToggleVisibility;
}
private void ToggleVisibility(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Visibility")
this.Visibility = _e.MyVisibility;
}
//Notifier class is just used to pass the property changed event back to the column container Dependency Object, leaving it as a private inner class for now
private class Notifier : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
private set { SetValue(MyVisibilityProperty, value); }
}
public static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(Notifier), new PropertyMetadata(MyVisibilityChanged));
private static void MyVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var n = d as Notifier;
if (n != null)
{
n.MyVisibility = (Visibility) e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
}
}
}
}
The datagrid column inherits from DependencyObject instead of FrameworkElement. In WPF this would be no big deal... but in silverlight you can only bind to FrameworkElement objects. So you get the descriptive error message of AG_E_PARSER_BAD_PROPERTY_VALUE when you try.
I don't know how much this will help, but I've run into the lack of dependency property problem with data grid columns myself in my latest project. What I did to get around it, was to create an event in the grid column view model, then when the grid is being assembled in the client, use a closure to subscribe the grid column to the column view model. My particular problem was around width. It starts with the view model class for the grid column, which looks something like this pseudo-code:
public delegate void ColumnResizedEvent(double width);
public class GridColumnViewModel : ViewModelBase
{
public event ColumnResizedEvent ColumnResized;
public void Resize(double newContainerWidth)
{
// some crazy custom sizing calculations -- don't ask...
ResizeColumn(newWidth);
}
public void ResizeColumn(double width)
{
var handler = ColumnResized;
if (handler != null)
handler(width);
}
}
Then there's the code that assembles the grid:
public class CustomGrid
{
public CustomGrid(GridViewModel viewModel)
{
// some stuff that parses control metadata out of the view model.
// viewModel.Columns is a collection of GridColumnViewModels from above.
foreach(var column in viewModel.Columns)
{
var gridCol = new DataGridTextColumn( ... );
column.ColumnResized += delegate(double width) { gridCol.Width = new DataGridLength(width); };
}
}
}
When the datagrid is resized in the application, the resize event is picked up and calls the resize method on the viewmodel the grid is bound to. This in turn calls the resize method of each grid column view model. The grid column view model then raises the ColumnResized event, which the data grid text column is subscribed to, and it's width is updated.
I realise this isn't directly solving your problem, but it was a way I could "bind" a view model to a data grid column when there are no dependency properties on it. The closure is a simple construct that nicely encapsulates the behaviour I wanted, and is quite understandable to someone coming along behind me. I think it's not too hard to imagine how it could be modified to cope with visibility changing. You could even wire the event handler up in the load event of the page/user control.
Chris Mancini,
you do not create binding to "Binding" property of data grid column. Well, you write "{Binding User.UserName}", but it doesn't create binding, because (as zachary said) datagrid column doesn't inherit from FrameworkElement and hasn't SetBinding method.
So expression "{Binding User.UserName}" simply creates Binding object and assign it to Binding property of column (this property is type of Binding).
Then datagrid column while generates cells content (GenerateElement - protected method) uses this Binding object to set binding on generated elements (e.g. on Text property of generated TextBlock) which are FrameworkElements
GreatTall1's solution is great, but it need to bit change to make it work.
var n = d as Notifier;
if (n != null)
{
//Assign value in the callback will break the binding.
//n.MyVisibility = (Visibility)e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
Note that the problem isn't just as simple as 'Visibility' not being a dependency property. In a DataGrid the columns aren't part of the visual 'tree' so you can't use AncestorType even in WPF (or Silverlight 5).
Here's a couple WPF related links (please comment if any of these work for Silverlight - sorry I don't have time to test now)
Has a really nice explanation of the problem and failures of certain solutions (and a clever solution):
http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
And a couple StackOverflow questions:
WPF Hide DataGridColumn via a binding
Binding Visible property of a DataGridColumn in WPF DataGrid
This works on a data grid template column:
public class ExtendedDataGridColumn : DataGridTemplateColumn
{
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(DataGridTemplateColumn), new PropertyMetadata(Visibility.Visible, VisibilityChanged));
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((DataGridTemplateColumn)d != null)
{
((DataGridTemplateColumn)d).Visibility = (Visibility)e.NewValue;
}
}
}
From your MyDataGridTextColumn class, you could get the surrounding DataGrid.
Then you get your ViewModel out of the DataContext of the DataGrid and add a handler to the PropertyChanged event of your ViewModel. In the handler you just check for the property name and its value and change the Visibility of the Column accordingly.
Its not quite the best solution, but it should work ;)

Resources