好吧,我可能会问一个古老的问题,但我没有在任何一个问题中描述我的情景。
我有一个可以包含多个子对象的Oject。例如。项目obejct可以有多个Resource对象。我有一个带有超级子对象集的ObservaleCollection(在我的案例中是Resource对象)。我在Project对象中还有另一个包含现有子项的ObservableCollection。
在WPF Windows应用程序中向用户显示此内容的最佳方法是什么?我还需要为他们提供一种方法来定位映射。
我最初的想法是使用经典的双列表方法,有两个列表框,但我不确定单独操作视图层是多么容易。
[Resoure Collection] [Resoure Collection in a Project]
-------------------- ---------------------------------
|Resource 1 | > |Resource 3 |
|Resource 2 | >> |Resource 4 |
|Resource 5 | < | |
|Resource 6 | << | |
|Resource 7 | | |
我需要类似的UI来进行4个不同对象的类似映射。我试图将其移动到用户控件,但看起来我在UserControl中没有Generic集合(私有ObservableCollection)。
来自经验成员的任何想法?
/ * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * **** / 编辑:这是我到目前为止所做的,请注意我将使用UserControl,因为我需要在多个屏幕中使用相同的UI,我觉得UserCOntrol将为我提供更易于管理的代码。
用户控件的XAML
<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"
mc:Ignorable="d"
x:Class="TimeTracker.ItemsSelectionLists"
x:Name="ItemsSelectionControl">
<Grid x:Name="LayoutRoot">
<Grid Background="#FFF9FDFD"
Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="SourceHeading"
Grid.Column="0"
Margin="8,8,0,0"
TextWrapping="Wrap"
Text="Whole Team"
VerticalAlignment="Top" />
<ListBox x:Name="SourceItemsList"
Grid.Column="0"
Margin="8,30,8,8"
MinWidth="150"
SelectionMode="Multiple"
ItemsSource="{Binding Path=Collection1}"/>
<StackPanel Grid.Column="1"
Margin="0"
Orientation="Vertical"
VerticalAlignment="Center">
<Button Content=">"
Height="25"
Width="25" />
<Button Content=">>"
Height="25"
Width="25" />
<Button Content="<"
Height="25"
Width="25" />
<Button Content="<<"
Height="25"
Width="25" />
</StackPanel>
<TextBlock x:Name="TargetHeading"
Grid.Column="2"
Margin="8,8,8,0"
TextWrapping="Wrap"
Text="Current Team"
VerticalAlignment="Top" />
<ListBox x:Name="SelectedItemsList"
Grid.Column="2"
Margin="8,30,8,8"
MinWidth="150"
ItemsSource="{Binding Path=Collection2}"/>
</Grid>
</Grid>
</UserControl>
代码:
/// <summary>
/// Interaction logic for ItemsSelectionLists.xaml
/// </summary>
public partial class ItemsSelectionLists: UserControl
{
[Bindable(true)]
internal ObservableCollection<TrackerItem> SourceList
{
get
{
return _vm.Collection1;
}
set
{
_vm.Collection1 = value;
}
}
private readonly ViewModel _vm;
public ItemsSelectionLists()
{
this.InitializeComponent();
_vm = new ViewModel();
this.DataContext = _vm;
}
}
public class ViewModel : INotifyPropertyChanged
{
#region Properties
private ObservableCollection<TrackerItem> _collection1;
/// <summary>
/// This is the first collection.
/// </summary>
internal ObservableCollection<TrackerItem> Collection1
{
get
{
return _collection1;
}
set
{
if (value != _collection1)
{
_collection1 = value;
NotifyPropertyChanged("Collection1");
}
}
}
private ObservableCollection<TrackerItem> _collection2;
/// <summary>
/// This is the second collection.
/// </summary>
internal ObservableCollection<TrackerItem> Collection2
{
get
{
return _collection2;
}
set
{
if (value != _collection2)
{
_collection2 = value;
NotifyPropertyChanged("Collection2");
}
}
}
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public ViewModel()
{
// Create initial collections.
// Populate first collection with sample data
_collection1 = new ObservableCollection<TrackerItem>();
// Seconf collection is empty
_collection2 = new ObservableCollection<TrackerItem>();
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
主窗口
<TabItem Header="Resource Allocation">
<local:ItemsSelectionLists x:Name="ResourceSelection"/>
</TabItem>
代码
ResourceSelection.SourceList = MainObject.Resources;
//error CS0029: Cannot implicitly convert type 'System.Collections.ObjectModel.ObservableCollection<TimeTracker.Resource>' to 'System.Collections.ObjectModel.ObservableCollection<TimeTracker.TrackerItem>'
答案 0 :(得分:1)
它没有优雅或抛光,但这是一个可行的样本。如果我做了这个抛光,我会实现真正的MVVM,但作为一个示例,这将让你开始。
XAML:
<Window x:Class="TwoListboxes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="List1" Grid.Column="0"
Height="200" Margin="10"
SelectionMode="Multiple"
ItemsSource="{Binding Path=Collection1}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Content=">" Width="60" Click="MoveRightEvent" />
<Button Content=">>" Width="60" Click="MoveAllRightEvent" />
<Button Content="<<" Width="60" Click="MoveAllLeftEvent" />
<Button Content="<" Width="60" Click="MoveLeftEvent" />
</StackPanel>
<ListBox x:Name="List2" Grid.Column="2"
Height="200" Margin="10"
ItemsSource="{Binding Path=Collection2}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
代码隐藏:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Members
private readonly ViewModel<TrackerItem> _vm;
#endregion
public MainWindow()
{
// Get viewmodel and set context
_vm = new ViewModel<TrackerItem>();
_vm.Collection1 = new ObservableCollection<TrackerItem>
{
new TrackerItem { Name = "Item1", Value = "1"},
new TrackerItem { Name = "Item2", Value = "2"},
new TrackerItem { Name = "Item3", Value = "3"},
new TrackerItem { Name = "Item4", Value = "4"},
new TrackerItem { Name = "Item5", Value = "5"},
new TrackerItem { Name = "Item6", Value = "6"},
new TrackerItem { Name = "Item7", Value = "7"},
new TrackerItem { Name = "Item8", Value = "8"},
new TrackerItem { Name = "Item9", Value = "9"},
new TrackerItem { Name = "Item10", Value = "10"}
};
this.DataContext = _vm;
// Initialize UI
InitializeComponent();
}
/// <summary>
/// Moves selected items in a list from one collection to another.
/// </summary>
/// <param name="list"></param>
/// <param name="source"></param>
/// <param name="destination"></param>
private void MoveItems(ListBox list,
ObservableCollection<TrackerItem> source,
ObservableCollection<TrackerItem> destination)
{
if (list.SelectedItems.Count > 0)
{
// List for items to be removed.
var hitList = new List<TrackerItem>();
// Move items
foreach (var selectedItem in list.SelectedItems)
{
var item = selectedItem as TrackerItem;
if (item != null)
{
// Tag item for removal
hitList.Add(item);
// Check if item is in target list
var targetList = (from p in destination
where p == item
select p).ToList();
// Add to destination
if (!targetList.Any())
{
destination.Add(item);
}
}
}
// Remove items
foreach (var hitItem in hitList)
{
// Remove item
source.Remove(hitItem);
}
}
}
/// <summary>
/// Moves all items from one list to another.
/// </summary>
/// <param name="source"></param>
/// <param name="destination"></param>
private void MoveAllItems(
ObservableCollection<TrackerItem> source,
ObservableCollection<TrackerItem> destination)
{
// List for items to be removed.
var hitList = new List<TrackerItem>();
// Move items
foreach (var item in source)
{
if (item != null)
{
// Tag item for removal
hitList.Add(item);
// Check if item is in target list
var targetList = (from p in destination
where p == item
select p).ToList();
// Add to destination
if (!targetList.Any())
{
destination.Add(item);
}
}
}
// Remove items
foreach (var hitItem in hitList)
{
// Remove item
source.Remove(hitItem);
}
}
/// <summary>
/// Click event: moves selected items to the right.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MoveRightEvent(object sender, RoutedEventArgs e)
{
MoveItems(List1, _vm.Collection1, _vm.Collection2);
}
/// <summary>
/// Click event: moves all items to the right..
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MoveAllRightEvent(object sender, RoutedEventArgs e)
{
MoveAllItems(_vm.Collection1, _vm.Collection2);
}
/// <summary>
/// Click event: moves all items to the left.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MoveAllLeftEvent(object sender, RoutedEventArgs e)
{
MoveAllItems(_vm.Collection2, _vm.Collection1);
}
/// <summary>
/// Click event: moves selected items to the left.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MoveLeftEvent(object sender, RoutedEventArgs e)
{
MoveItems(List2, _vm.Collection2, _vm.Collection1);
}
}
ViewModel:
public class ViewModel<T> : INotifyPropertyChanged
{
#region Properties
private ObservableCollection<T> _collection1;
/// <summary>
/// This is the first collection.
/// </summary>
public ObservableCollection<T> Collection1
{
get { return _collection1; }
set
{
if (value != _collection1)
{
_collection1 = value;
NotifyPropertyChanged("Collection1");
}
}
}
private ObservableCollection<T> _collection2;
/// <summary>
/// This is the second collection.
/// </summary>
public ObservableCollection<T> Collection2
{
get { return _collection2; }
set
{
if (value != _collection2)
{
_collection2 = value;
NotifyPropertyChanged("Collection2");
}
}
}
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public ViewModel()
{
// Create initial collections.
// Populate first collection with sample data
_collection1 = new ObservableCollection<T>();
// Seconf collection is empty
_collection2 = new ObservableCollection<T>();
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
TrackerItem
public class TrackerItem : INotifyPropertyChanged
{
private string _name;
/// <summary>
/// A name.
/// </summary>
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _value;
/// <summary>
/// A value.
/// </summary>
public string Value
{
get { return _value; }
set
{
if (value != _value)
{
_value = value;
NotifyPropertyChanged("Value");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
答案 1 :(得分:0)
好的,感谢Reflection和XCalibur的viewmodel想法,我能够完成我的用户控件,它可以显示两个相同类型的通用可观察集合的内容。控件的使用者可以指定列表框应该显示的对象的属性。集合数据将复制到本地集合,因此对集合所做的任何更改都不会更改输入集合。
<强> XAML:强>
<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">
<UserControl.Resources>
<local:DummyConverter x:Key="DummyConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid Background="#FFF9FDFD"
Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Margin="8,8,0,0"
TextWrapping="Wrap"
Text="{Binding Path=LeftHeader, RelativeSource={RelativeSource AncestorType=UserControl}}"
VerticalAlignment="Top" />
<ListBox x:Name="LeftItemsList"
Grid.Column="0"
Margin="8,30,8,8"
MinWidth="150"
SelectionMode="Multiple"
ItemsSource="{Binding Path=LeftCollection}" />
<StackPanel Grid.Column="1"
Margin="0"
Orientation="Vertical"
VerticalAlignment="Center">
<Button Content=">"
Height="25"
Width="25"
Click="Button_Click" />
<Button Content=">>"
Height="25"
Width="25"
Click="Button_Click" />
<Button Content="<"
Height="25"
Width="25"
Click="Button_Click" />
<Button Content="<<"
Height="25"
Width="25"
Click="Button_Click" />
</StackPanel>
<TextBlock Grid.Column="2"
Margin="8,8,8,0"
TextWrapping="Wrap"
Text="{Binding Path=RightHeader, RelativeSource={RelativeSource AncestorType=UserControl}}"
VerticalAlignment="Top" />
<ListBox x:Name="RightItemsList"
Grid.Column="2"
Margin="8,30,8,8"
MinWidth="150"
SelectionMode="Multiple"
ItemsSource="{Binding Path=RightCollection}" />
</Grid>
</Grid>
</UserControl>
用户控制代码
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 ">":
MoveItems.Invoke(dataSource, new object[] { LeftItemsList, true });
break;
case ">>":
MoveAllItems.Invoke(dataSource, new object[] { true });
break;
case "<":
MoveItems.Invoke(dataSource, new object[] { RightItemsList, false });
break;
case "<<":
MoveAllItems.Invoke(dataSource, new object[] { false });
break;
}
}
}
<强>视图模型强>
public class ViewModel<T> : DataTemplateSelector, INotifyPropertyChanged
{
#region Properties
//this is just a placeholder for the collection, no changes will be made to this collection
private ObservableCollection<T> leftCollectionRef;
//local collection
private ObservableCollection<T> leftCollection;
public ObservableCollection<T> 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<T> rightCollectionRef;
private ObservableCollection<T> rightCollection;
public ObservableCollection<T> 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<T>();
// internal collection, this will get items copied over from reference target collection
rightCollection = new ObservableCollection<T>();
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 > 0)
{
// List for items to be removed.
var hitList = new List<T>();
// 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 && e.NewItems.Count > 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 && e.OldItems.Count > 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 =
@"<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<TextBlock Margin=""2"" TextWrapping=""Wrap"" Text=""{Binding Path=" + this.bindingMember + @", Mode=OneWay}""/>
</DataTemplate>";
StringReader stringReader = new StringReader(dataTemplate);
XmlReader xmlReader = XmlReader.Create(stringReader);
return XamlReader.Load(xmlReader) as DataTemplate;
}
#endregion
}
使用控件:
<TabItem Header="Resource Allocation">
<local:ItemsSelectionLists x:Name="ProjectResourceMap" LeftHeader="Whole Team" RightHeader="Current Project Team"/>
</TabItem>
<TabItem Header="Tasks for the Project">
<local:ItemsSelectionLists x:Name="ProjectTaskMap" Margin="0" d:LayoutOverrides="Width" LeftHeader="All Tasks" RightHeader="Current Project Tasks"/>
</TabItem>
ViewModel<Resource> ProjectResource = new ViewModel<Resource>();
ProjectResource.BindingMember = "ResourceName";
this.ProjectResourceMap.DataSource = ProjectResource;
ProjectResource.LeftCollection = timeTracker.Resources;
ViewModel<Task> ProjectTasks = new ViewModel<Task>();
ProjectTasks.BindingMember = "TaskName";
this.ProjectTaskMap.DataSource = ProjectTasks;
ProjectTasks.LeftCollection = timeTracker.Tasks;