Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Okay, thanks to Reflection and XCalibur's viewmodel idea, I am able to complete my user control, which can show contents of two generic observable collections of same type. The consumer of the control can specify the property of the objects listboxes should show. The collection data would be copied to local collection, and hence any changes made to the collections won't change the input collections.</p> <p><strong>XAML:</strong></p> <pre><code>&lt;UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TimeTracker" x:Class="TimeTracker.ItemsSelectionLists" x:Name="ItemsSelectionControl"&gt; &lt;UserControl.Resources&gt; &lt;local:DummyConverter x:Key="DummyConverter" /&gt; &lt;/UserControl.Resources&gt; &lt;Grid x:Name="LayoutRoot"&gt; &lt;Grid Background="#FFF9FDFD" Margin="0,0,0,0"&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition /&gt; &lt;ColumnDefinition Width="Auto" /&gt; &lt;ColumnDefinition /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;TextBlock Grid.Column="0" Margin="8,8,0,0" TextWrapping="Wrap" Text="{Binding Path=LeftHeader, RelativeSource={RelativeSource AncestorType=UserControl}}" VerticalAlignment="Top" /&gt; &lt;ListBox x:Name="LeftItemsList" Grid.Column="0" Margin="8,30,8,8" MinWidth="150" SelectionMode="Multiple" ItemsSource="{Binding Path=LeftCollection}" /&gt; &lt;StackPanel Grid.Column="1" Margin="0" Orientation="Vertical" VerticalAlignment="Center"&gt; &lt;Button Content="&amp;gt;" Height="25" Width="25" Click="Button_Click" /&gt; &lt;Button Content="&amp;gt;&amp;gt;" Height="25" Width="25" Click="Button_Click" /&gt; &lt;Button Content="&amp;lt;" Height="25" Width="25" Click="Button_Click" /&gt; &lt;Button Content="&amp;lt;&amp;lt;" Height="25" Width="25" Click="Button_Click" /&gt; &lt;/StackPanel&gt; &lt;TextBlock Grid.Column="2" Margin="8,8,8,0" TextWrapping="Wrap" Text="{Binding Path=RightHeader, RelativeSource={RelativeSource AncestorType=UserControl}}" VerticalAlignment="Top" /&gt; &lt;ListBox x:Name="RightItemsList" Grid.Column="2" Margin="8,30,8,8" MinWidth="150" SelectionMode="Multiple" ItemsSource="{Binding Path=RightCollection}" /&gt; &lt;/Grid&gt; &lt;/Grid&gt; &lt;/UserControl&gt; </code></pre> <p><strong>User control code</strong></p> <pre><code>public partial class ItemsSelectionLists : UserControl { #region properties public string LeftHeader { get { return (string) GetValue(LeftHeaderProperty); } set { SetValue(LeftHeaderProperty, value); } } // Using a DependencyProperty as the backing store for LeftHeader. This enables animation, styling, binding, etc... public static readonly DependencyProperty LeftHeaderProperty = DependencyProperty.Register("LeftHeader", typeof(string), typeof(ItemsSelectionLists), new UIPropertyMetadata("Left List Header")); public string RightHeader { get { return (string) GetValue(RightHeaderProperty); } set { SetValue(RightHeaderProperty, value); } } // Using a DependencyProperty as the backing store for RightHeader. This enables animation, styling, binding, etc... public static readonly DependencyProperty RightHeaderProperty = DependencyProperty.Register("RightHeader", typeof(string), typeof(ItemsSelectionLists), new UIPropertyMetadata("Right List Header")); private object dataSource; public object DataSource { get { return dataSource; } set { if (!value.GetType().FullName.StartsWith("TimeTracker.ViewModel")) throw new ArgumentException("DataSource is not an instance of ViewModel"); if (dataSource != value) { dataSource = value; this.DataContext = this.DataSource; DataTemplateSelector templateSelector = dataSource as DataTemplateSelector; this.LeftItemsList.ItemTemplateSelector = templateSelector; this.RightItemsList.ItemTemplateSelector = templateSelector; } } } #endregion public ItemsSelectionLists() : base() { this.InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { var button = sender as Button; var type = dataSource.GetType(); var MoveItems = type.GetMethod("MoveItems"); var MoveAllItems = type.GetMethod("MoveAllItems"); switch (button.Content.ToString()) { case "&gt;": MoveItems.Invoke(dataSource, new object[] { LeftItemsList, true }); break; case "&gt;&gt;": MoveAllItems.Invoke(dataSource, new object[] { true }); break; case "&lt;": MoveItems.Invoke(dataSource, new object[] { RightItemsList, false }); break; case "&lt;&lt;": MoveAllItems.Invoke(dataSource, new object[] { false }); break; } } } </code></pre> <p><strong>ViewModel</strong></p> <pre><code>public class ViewModel&lt;T&gt; : DataTemplateSelector, INotifyPropertyChanged { #region Properties //this is just a placeholder for the collection, no changes will be made to this collection private ObservableCollection&lt;T&gt; leftCollectionRef; //local collection private ObservableCollection&lt;T&gt; leftCollection; public ObservableCollection&lt;T&gt; LeftCollection { get { return leftCollection; } set { if (value != leftCollectionRef) { //remove subscription to previous collection if (leftCollectionRef != null) leftCollectionRef.CollectionChanged -= new NotifyCollectionChangedEventHandler(Ref_CollectionChanged); leftCollectionRef = value; leftCollection.Clear(); foreach (var item in leftCollectionRef) { if (rightCollection.IndexOf(item) == -1) leftCollection.Add(item); } NotifyPropertyChanged("LeftCollection"); //subscribe to chnages in new collection leftCollectionRef.CollectionChanged += new NotifyCollectionChangedEventHandler(Ref_CollectionChanged); } } } //this is just a placeholder for the collection, no changes will be made to this collection private ObservableCollection&lt;T&gt; rightCollectionRef; private ObservableCollection&lt;T&gt; rightCollection; public ObservableCollection&lt;T&gt; RightCollection { get { return rightCollection; } set { if (value != rightCollectionRef) { //remove subscription to previous collection if (rightCollectionRef != null) rightCollectionRef.CollectionChanged -= new NotifyCollectionChangedEventHandler(Ref_CollectionChanged); rightCollectionRef = value; rightCollection.Clear(); foreach (var item in rightCollectionRef) { if (leftCollection.IndexOf(item) == -1) rightCollection.Add(item); } NotifyPropertyChanged("RightCollection"); rightCollectionRef.CollectionChanged += new NotifyCollectionChangedEventHandler(Ref_CollectionChanged); } } } private string bindingMember; public string BindingMember { get { return bindingMember; } set { var mem = typeof(T).GetProperty(value); if (mem == null) throw new ArgumentException("No Member " + value + " found in " + this.GetType().FullName); if (bindingMember != value) { bindingMember = value; NotifyPropertyChanged("BindingMember"); } } } #endregion #region Constructors public ViewModel() : base() { // internal collection, this will get items copied over from reference source collection leftCollection = new ObservableCollection&lt;T&gt;(); // internal collection, this will get items copied over from reference target collection rightCollection = new ObservableCollection&lt;T&gt;(); bindingMember = ""; } #endregion #region Movements public void MoveItems(ListBox list, bool LeftToRight) { var source = leftCollection; var target = rightCollection; if (!LeftToRight) { target = leftCollection; source = rightCollection; } if (list.SelectedItems.Count &gt; 0) { // List for items to be removed. var hitList = new List&lt;T&gt;(); // Move items foreach (T item in list.SelectedItems) { if (item != null) { // Tag item for removal hitList.Add(item); // Check if item is in target list if (target.IndexOf(item) == -1) { target.Add(item); } } } // Remove items foreach (var hitItem in hitList) { source.Remove(hitItem); } } } public void MoveAllItems(bool LeftToRight) { if (LeftToRight) { rightCollection.Clear(); foreach (var item in leftCollection) { RightCollection.Add(item); } leftCollection.Clear(); } else { leftCollection.Clear(); foreach (var item in rightCollection) { leftCollection.Add(item); } rightCollection.Clear(); } } #endregion #region collection-monitor private void Ref_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null &amp;&amp; e.NewItems.Count &gt; 0) { var target = leftCollection; if (sender == leftCollectionRef) target = leftCollection; else target = rightCollection; foreach (T item in e.NewItems) { target.Add(item); } } //try remove from both collections, since the item may have moved to right or left collections if (e.OldItems != null &amp;&amp; e.OldItems.Count &gt; 0) { foreach (T item in e.OldItems) { leftCollection.Remove(item); } foreach (T item in e.OldItems) { rightCollection.Remove(item); } } } #endregion #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } #endregion #region templateselector public override DataTemplate SelectTemplate(object item, DependencyObject container) { string dataTemplate = @"&lt;DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""&gt; &lt;TextBlock Margin=""2"" TextWrapping=""Wrap"" Text=""{Binding Path=" + this.bindingMember + @", Mode=OneWay}""/&gt; &lt;/DataTemplate&gt;"; StringReader stringReader = new StringReader(dataTemplate); XmlReader xmlReader = XmlReader.Create(stringReader); return XamlReader.Load(xmlReader) as DataTemplate; } #endregion } </code></pre> <p>Using the control:</p> <pre><code>&lt;TabItem Header="Resource Allocation"&gt; &lt;local:ItemsSelectionLists x:Name="ProjectResourceMap" LeftHeader="Whole Team" RightHeader="Current Project Team"/&gt; &lt;/TabItem&gt; &lt;TabItem Header="Tasks for the Project"&gt; &lt;local:ItemsSelectionLists x:Name="ProjectTaskMap" Margin="0" d:LayoutOverrides="Width" LeftHeader="All Tasks" RightHeader="Current Project Tasks"/&gt; &lt;/TabItem&gt; ViewModel&lt;Resource&gt; ProjectResource = new ViewModel&lt;Resource&gt;(); ProjectResource.BindingMember = "ResourceName"; this.ProjectResourceMap.DataSource = ProjectResource; ProjectResource.LeftCollection = timeTracker.Resources; ViewModel&lt;Task&gt; ProjectTasks = new ViewModel&lt;Task&gt;(); ProjectTasks.BindingMember = "TaskName"; this.ProjectTaskMap.DataSource = ProjectTasks; ProjectTasks.LeftCollection = timeTracker.Tasks; </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload