WPF Tab Into Databound ItemsControl - c#

In my WPF application, I have a databound TextBox and a databound ItemsControl. The contents of the ItemsControl is determined by the contents of the TextBox. I want to be able to type a value into the TextBox, press tab and enter the first item in the ItemsControl (created from the value in the TextBox). The problem I am having is that the tab action is evaluated before WPF creates the templated items in the ItemsControl. The following code demonstrates this problem:
<Window x:Class="BindingExample.Window1" x:Name="SelfControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:BindingExample" Title="Window1" Height="300" Width="400">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:ChildClass}">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</Window.Resources>
<StackPanel DataContext="{Binding ElementName=SelfControl}" Focusable="False">
<Label Content="Options: A, B, C" />
<TextBox Text="{Binding Object.Value}" />
<ItemsControl Margin="16,0,0,0" ItemsSource="{Binding Object.Children}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBox Text="Box2" />
</StackPanel>
</Window>
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingExample
{
public partial class Window1
{
public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register("Object", typeof(ParentClass), typeof(Window1));
public ParentClass Object
{
get { return GetValue(ObjectProperty) as ParentClass; }
set { SetValue(ObjectProperty, value); }
}
public Window1()
{
InitializeComponent();
Object = new ParentClass();
}
}
public class ParentClass : INotifyPropertyChanged
{
private string value;
public string Value
{
get { return value; }
set { this.value = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Children")); }
}
public IEnumerable<ChildClass> Children
{
get
{
switch (Value.ToUpper())
{
case "A": return new ChildClass[] { "A-1", "A-2", "A-2" };
case "B": return new ChildClass[] { "B-1", "B-2", "B-3" };
case "C": return new ChildClass[] { "C-1", "C-2", "C-2" };
default: return new ChildClass[] { "Unknown" };
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ChildClass
{
public string Value { get; set; }
public static implicit operator ChildClass(string value) { return new ChildClass { Value = value }; }
}
}
In this code, I would like to type "A" into the first TextBox, press tab, and have the focus shift to the child TextBox with the text "A-1". Instead the focus skips to the TextBox with the text "Box2". How can I achieve the behavior I am looking for here?
Note: As Julien Poulin pointed out, it is possible to make this work by switching the UpdateSourceTrigger on the TextBox to PropertyChanged. This only works, however, if "as you type" binding is acceptable. In my case, I would also like to do the set the value and tab with one keystroke. Is there some way to force the ItemsControl to create its templated items on-demand?

Try to set the UpdateMode of the TextBox to PropertyChanged so the underlying value is set when you type a new value instead of when the TextBox loses focus:
<TextBox Text="{Binding Path=Object.Value, UpdateMode=PropertyChanged}" />

Here is an alternative solution. It's a bit of a hack, but appears to work. Since the templated objects in the ItemsControl aren't created until execution on the main thread pauses, this code catches the tab, updates the binding, and sets a timer to move the focus once the items have a chance to be created.
...
<TextBox Text="{Binding Object.Value}" KeyDown="TextBox_KeyDown" />
...
public partial class Window1
{
private DispatcherTimer timer;
...
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab && e.KeyboardDevice.Modifiers != ModifierKeys.Shift)
{
e.Handled = true;
var textbox = (TextBox)sender;
textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
(timer = new DispatcherTimer(
new TimeSpan(100000), // 10 ms
DispatcherPriority.Normal,
delegate
{
textbox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
timer.Stop();
}, Dispatcher)).Start();
}
}
}
The only problem I see with this code is that the ItemsControl might take longer than 10 ms to populate, in which case Tab would jump over those items. Is there any way of detecting whether or not the creation of ItemsControl items has occurred?

Related

WPF DependencyProperty OnPropertyChanged returns default value as NewValue if using DataTemplate on UserControl

I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}

Getting IsChecked Property of a CheckBox in a ListBox

So many examples found and none fit! My list box is a list of Result objects. Results can be checked or unchecked in a listbox to mark them as 'Allowed to 'transmit.
<ListBox
x:Name="FileListBox"
ItemsSource="{Binding TestResults}"
ItemTemplate="{StaticResource FileListTemplate}"
SelectionMode="Single"
SelectedItem="{Binding FileListSelected}"
Background="#FFFFFBE2" />
The FileListTemplate
<DataTemplate x:Key="FileListTemplate">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".2*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding FileName}" />
<TextBlock Grid.Column="1"
Text="Machine">
</TextBlock>
<CheckBox x:Name="UploadOK"
Grid.Column="2"
HorizontalAlignment="Right"
IsChecked="{Binding CanUpload, Mode=TwoWay}" />
</Grid>
</DataTemplate>
I took out a lot of formatting code to reduce the clutter. So when the check box is checked (or un checked) I need to set a boolean on the object to true or false. But I do not want the ListItem selected just because the checkbox is selected. When the ListItem is selected something else happens. Here is the code for that.
public TestResult FileListSelected
{
get
{
return selectedItem;
}
set
{
if (value == selectedItem)
return;
selectedItem = value;
if (!Workspaces.Any(p => p.DisplayName == value.FileName))
{
this.DisplayTestResult(value as TestResult);
}
base.RaisePropertyChanged("FileListSelected");
}
}
And here is the code I bound to for the Checkbox (although it didn't work).
public bool CanUpload
{
get { return selectedItem.CanUpload; }
set
{
selectedItem.CanUpload = value;
}
}
I appreciate you looking at this.
Internal Class TestResult
{
...
private bool _canUpload;
public bool CanUpload
{
get { return _canUpload; }
set
{
_canUpload = value;
base.RaisePropertyChanged("CanUpload");
}
}
}
When working with MVVM always check for the following:
Add using System.ComponentModel; to your ViewModelClass
Inherit from INotifyPropertyChanged
Always check your DataContext and see the Output Window for BindingErrors
Create Bindings like this:
Example Property:
public string Example
{
get { return _example; }
set
{
_example= value;
OnPropertyChanged();
}
}
this will call OnPropertyChanged automatically every time a new value is assigned (not updated automaticaly once it changes from some other location!)
Make sure your Implementation of INotifyPropertyChanged looks like this:
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
for that you also need using System.Runtime.CompilerServices;
Other options to get your code working:
Your TestResults sould be an ObservableCollection<TestResult>
TestResult should have a property for CanUpload and FileName and inherit from INotifyPropertyChanged
Then on your MainViewModel for example on and ButtonClick your can get the selected files like this:
private List<string> GetSelectedFiles()
{
return TestResults.Where(result => result.CanUpload == true).Select(r => r.FileName).ToList());
}
Note:
FileListSelected is a Property of your ListBox's DataContext which is different to the DataContext of an entry (or at least should be).
FileListSelected will then return the selected Item of your ItemsSource.
Maybe you can comment on this problem with the row selection/checkbox check and add some detail so I can help you more.
EDIT: Notify MainWindowViewModel about CheckBox State Changes:
I see two possible approaches here:
USING EVENT
Add this to your TestResult class:
public delegate void CheckBoxStateChangedHandler(object sender, CheckBoxStateChangedEventArgs e);
public event CheckBoxStateChangedHandler CheckBoxStateChanged;
public class CheckBoxStateChangedEventArgs
{
bool CheckBoxChecked { get; set; }
}
Make sure that on creation of a new TestResult in your MainViewModel you subscribe to that event;
testResult.CheckBoxStateChanged += CheckBox_StateChanged;
Handle what you want to do once the state is changed in CheckBox_StateChanged. Note that the argument e contains the boolean (Checked) and the corresponding TestResult as the sender.
You simply invoke your new Event in the Setter of your CheckBox.Checked Binding:
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
CheckBoxStateChanged.Invoke(this, new CheckBoxStateChangedEventArgs() { CheckBoxChecked = value })
}
}
CALL METHOD ON MAINWINDOWVIEWMODEL
for that you need o create a static object of your MainWindowViewModel (in your MainViewModel) - don't forget to assigne a value once you create your MainWindowViewModel.
public static MainViewModel Instance { get; set; }
then simply add a public Method as you need:
public void CheckBoxValueChanged(bool value, TestResult result)
{
//Do whatever
}
you can also call in from the same spot as the event from above is invoked.
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
MainWindowViewModel.Instance.CheckBoxValueChanged(value, this);
}
}

WPF behaviour to check/uncheck a checkbox in a list item

I have an application with several item controls (treeviews and others) which contain an item template with a checkbox inside. This checkbox checked state is bound to an IsChecked property of the item view model. This works correctly when clicking on the checkbox, but it's impossible to check/uncheck them with the keyboard (I believe this is due to the fact that the checkbox itself never gets the focus).
I like the solution proposed by DLeh in here: https://stackoverflow.com/a/24327765/352826 but I would like an improvement: Instead of having the behaviour calling a command on the base view model (the vm which contains the list of items), I would like the behaviour to directly act on the IsChecked property of the item.
My problem is that I don't know how to modify the behaviour or how to set up the binding on it, so that the behaviour can have access to the item's IsChecked property.
So, instead of the following:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ToggleSelectCommand="{Binding Data.ToggleSelectParticipantCommand, Source={StaticResource BindingProxy}}" />
</i:Interaction.Behaviors>
...
</DataGrid>
I would have something like this:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ItemsIsSelectedProperty="{Binding IsChecked}" />
</i:Interaction.Behaviors>
...
</DataGrid>
Update
I should add that my current implementation uses the PreviewKeyUp event in the itemscontrol and the following code behind implementation. The problem with this approach is that I have this code in many code behind files, so there is a lot of duplication. My goal is to replace this by a behaviour.
private void TreeView_OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
var tree = (TreeView) sender;
var item = tree.SelectedItem as IsSelectedViewModelBase;
if (item != null)
{
item.IsSelected = !item.IsSelected;
e.Handled = true;
}
}
}
Update 2
This is the item template and the checkbox is not checked when you press the space bar with the item selected.
<DataTemplate DataType="{x:Type viewModels:ItemViewModel}" >
<StackPanel Orientation="Horizontal" >
<CheckBox Focusable="False" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
<StackPanel Margin="2">
<TextBlock Text="{Binding Username}" FontWeight="Bold" />
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Not an answer to the asked question per se, but the following will illustrate that it is possible for a check box within a list item to receive keyboard focus.
I created a new WPF project using the default Visual Studio template. This creates a single window called "MainWindow". Here's the contents of the XAML and code-behind of that window.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thingies = new List<Thingy>
{
new Thingy { Name = "abc", IsChecked = false },
new Thingy { Name = "def", IsChecked = true },
new Thingy { Name = "ghi", IsChecked = false },
new Thingy { Name = "jkl", IsChecked = true },
new Thingy { Name = "mno", IsChecked = false },
}.ToArray();
DataContext = this;
}
public Thingy[] Thingies { get; private set; }
public class Thingy : INotifyPropertyChanged
{
public string Name { get; set; }
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked != value)
{
_isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("IsChecked"));
}
Console.WriteLine(Name + " = " + _isChecked);
}
}
}
bool _isChecked;
public event PropertyChangedEventHandler PropertyChanged;
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding Thingies}">
<ListBox.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Width="400">
<TextBlock Text="{Binding Name}" />
<CheckBox IsChecked="{Binding IsChecked}" />
</UniformGrid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>

How to set SeletedItems = null in a Listbox if i select a other Listbox

basically i'm trying to do THIS
but you can see it is not MVVM so i'm looking for a way to set SeletedItems = null or clear() depending on what's doable
because in my View i will got N ListBoxes and if he pressed a Button after selecting some Items i will change some properties of the SeletedItems but only for the last active Listbox
so i decided to use on SelectedItems Property for all the Listboxes but it doesn't work based on 2 problems i can't bind to to SelectedItems and based on this i can't test how to remove the selection from the other Listboxes
EDIT:
to give you an simple example:
XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection2}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="unselect" Width="80" Height="150"
HorizontalAlignment="Right" VerticalAlignment="Top"
Command="{Binding MyCommand}"/>
</StackPanel>
</Window>
Code
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace Test
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
private ObservableCollection<DetailVM> _SelectedListItems = new ObservableCollection<DetailVM>();
public ObservableCollection<DetailVM> SelectedListItems
{
get { return _SelectedListItems; }
set
{
_SelectedListItems = value;
OnPropertyChanged("SelectedListItems");
}
}
public List<DetailVM> Collection1 { get; set; }
public List<DetailVM> Collection2 { get; set; }
private RelayCommand _myCommand;
public ICommand MyCommand
{
get { return _myCommand?? (_myCommand= new RelayCommand(param => OnMyCommand())); }
}
public void OnMyCommand()
{
foreach DetailVM item in SelectedListItems
{
item.MyBackground ="Red";
}
}
public VM()
{
Collection1 = new List<DetailVM>();
Collection2 = new List<DetailVM>();
for (int i = 0; i < 10; i++)
{
Collection1.Add(new DetailVM { MyText = "C1ITEM " + i });
Collection2.Add(new DetailVM { MyText = "C2ITEM " + i });
}
}
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class DetailVM
{
public string MyText { get; set; }
public string MyBackground { get; set; }
}
}
The code above should change the color of the Textbox background to Red
if the user selected some Items in a Listbox and he should only be able to seleted Items in one Listbox at the same time
so how to do this? (bear in mind this is a simple example but i need this for N Listboxes which will be generated over a template)
First of all, I would recommend you to extend ListView so that it includes a bindable SelectedValues property (you cannot use the name SelectedItems since it's already a non-bindable property of ListView). Here's an example of how this can be achieved.
public class MultiSelectListView : ListView
{
// Using a DependencyProperty as backing store
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register("SelectedValues", typeof(IList), typeof(MultiSelectListView), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
public IList SelectedValues
{
get { return (IList)GetValue(SelectedValuesProperty); }
set { SetValue(SelectedValuesProperty, value); }
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// if selected items list implements INotifyCollectionChanged, we subscribe to its CollectionChanged event
var element = (MultiSelectListView)d;
if (e.OldValue != null && e.OldValue is INotifyCollectionChanged)
{
var list = e.OldValue as INotifyCollectionChanged;
list.CollectionChanged -= element.OnCollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged)
{
var list = e.NewValue as INotifyCollectionChanged;
list.CollectionChanged += element.OnCollectionChanged;
}
}
// when selection changes in the view, elements are added or removed from the underlying list
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (SelectedValues != null)
{
foreach (var item in e.AddedItems)
{
if (!SelectedValues.Contains(item))
SelectedValues.Add(item);
}
foreach (var item in e.RemovedItems)
{
if (SelectedValues.Contains(item))
SelectedValues.Remove(item);
}
}
base.OnSelectionChanged(e);
}
// when underlying list changes, we set the control's selected items to the contents of the list
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SelectedValues != null)
{
SetSelectedItems(SelectedValues);
}
}
}
Once you've done this you can control the behavior of a list's selected items through the viewmodel. Clearing the viewmodel list clears the selected items in the control.
Next you can subscribe to the collection changed event of your selected items lists (in the view model) and in the handler check whether you need to clear any of your lists.

Dependency Property in User Control works only on the first instance

I have several custom user controls in a window. They appear dynamically, like workspaces.
I need to add a dependency property on an itemscontrol to trigger a scrolldown when an item is being added to the bound observable collection to my itemscontrol, like so:
(usercontrol)
<ScrollViewer VerticalScrollBarVisibility="Auto" >
<ItemsControl Grid.Row="0" ItemsSource="{Binding Messages}" View:ItemsControlBehavior.ScrollOnNewItem="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
And the code of my dependency property :
public class ItemsControlBehavior
{
static readonly Dictionary<ItemsControl, Capture> Associations =
new Dictionary<ItemsControl, Capture>();
public static bool GetScrollOnNewItem(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollOnNewItemProperty);
}
public static void SetScrollOnNewItem(DependencyObject obj, bool value)
{
obj.SetValue(ScrollOnNewItemProperty, value);
}
public static readonly DependencyProperty ScrollOnNewItemProperty =
DependencyProperty.RegisterAttached(
"ScrollOnNewItem",
typeof(bool),
typeof(ItemsControl),
new UIPropertyMetadata(false, OnScrollOnNewItemChanged));
public static void OnScrollOnNewItemChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var mycontrol = d as ItemsControl;
if (mycontrol == null) return;
bool newValue = (bool)e.NewValue;
if (newValue)
{
mycontrol.Loaded += new RoutedEventHandler(MyControl_Loaded);
mycontrol.Unloaded += new RoutedEventHandler(MyControl_Unloaded);
}
else
{
mycontrol.Loaded -= MyControl_Loaded;
mycontrol.Unloaded -= MyControl_Unloaded;
if (Associations.ContainsKey(mycontrol))
Associations[mycontrol].Dispose();
}
}
static void MyControl_Unloaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
Associations[mycontrol].Dispose();
mycontrol.Unloaded -= MyControl_Unloaded;
}
static void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
var incc = mycontrol.Items as INotifyCollectionChanged;
if (incc == null) return;
mycontrol.Loaded -= MyControl_Loaded;
Associations[mycontrol] = new Capture(mycontrol);
}
class Capture : IDisposable
{
public ItemsControl mycontrol{ get; set; }
public INotifyCollectionChanged incc { get; set; }
public Capture(ItemsControl mycontrol)
{
this.mycontrol = mycontrol;
incc = mycontrol.ItemsSource as INotifyCollectionChanged;
incc.CollectionChanged +=incc_CollectionChanged;
}
void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
ScrollViewer sv = mycontrol.Parent as ScrollViewer;
sv.ScrollToBottom();
}
}
public void Dispose()
{
incc.CollectionChanged -= incc_CollectionChanged;
}
}
}
During the first instantiation of my user control, it works like a charm.
But when another user control of the same type is dynamically instantiated, the DependencyProperty is never attached anymore to my scrollviewer. Only the first instance will work correctly.
I know that dependency properties are static, but does that mean they can't work at the same time on several user control of the same type added to the window?
Update 02/03 : Here's how I set the viewmodel to the view (not programmatically) :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:testDp.ViewModel"
xmlns:View="clr-namespace:testDp.View">
<DataTemplate DataType="{x:Type vm:ChatTabViewModel}">
<View:ChatTabView />
</DataTemplate>
</ResourceDictionary>
even with x:shared = false in the datatemplate tag, it won't work.
But if I set the datacontext in a classic way like usercontrol.datacontext = new viewmodel(), it definitely work. But it's recommended to have a "shared" view, so how do we make dependency properties work with this "xaml" way of setting datacontext ?
Sorry, I couldn't reproduce your problem.
I started Visual C# 2010 Express, created a new 'WPF Application', added your XAML to a UserControl that I imaginatively titled UserControl1, and added your ItemsControlBehavior class. I then modified the MainWindow that VC# created for me as follows:
MainWindow.xaml (contents of <Window> element only):
<StackPanel Orientation="Vertical">
<Button Content="Add user control" Click="ButtonAddUserControl_Click" />
<Button Content="Add message" Click="ButtonAddMessage_Click" />
<StackPanel Orientation="Horizontal" x:Name="sp" Height="300" />
</StackPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<string> Messages { get; private set; }
public MainWindow()
{
InitializeComponent();
Messages = new ObservableCollection<string>() { "1", "2", "3", "4" };
DataContext = this;
}
private void ButtonAddUserControl_Click(object sender, RoutedEventArgs e)
{
sp.Children.Add(new UserControl1());
}
private void ButtonAddMessage_Click(object sender, RoutedEventArgs e)
{
Messages.Add((Messages.Count + 1).ToString());
}
}
I made no modifications to the XAML in your UserControl, nor to your ItemsControlBehavior class.
I found that no matter how many user controls were added, their ScrollViewers all scrolled down to the bottom when I clicked the 'Add message' button.
If you're only seeing the scroll-to-the-bottom behaviour on one of your user controls, then there must be something that you're not telling us.

Categories