我需要能够在带按钮的ListBox中显示数据列表,这些按钮可以上下移动项目并从ListBox中删除项目,并在数据模型中反映出来。
SampleDesign:http://bigriverrubber.com/_uploads/sites/2/usercontrollistbox.jpg
我计划在多个窗口中具有多个具有相同功能的ListBox,所以我想我可以使用其中的ListBox和按钮来制作一个UserControl,并让按钮修改数据。这样,我可以将ObservableCollection传递给UserControl,而不必每次都重新创建按钮。
但是,我发现,如果将这些项目绑定到ObservableCollection,则它们将无法移动,这对于我来说是必需的。根据我的读物,我需要修改集合。
但是如何从UserControl做到这一点?如果ObservableCollection的类型需要可变,以便ListBox可以显示许多类型的列表,我怎么可能希望将其定位为获得对ObservableCollection类中Move和Remove方法的访问权限?
我尝试将设置为ObservableCollection的ItemsSource转换为ObservableCollection
我尝试将其强制转换为ObservableCollection
我什至尝试过在具有ObservableCollection
我使用了ItemsControl,它根据找到的DataType更改了ListBox,但这仍然意味着我无论如何都要进行单独的按钮事件,那又是什么呢?
我会发布一些代码,但是看到我所做的一切没有任何效果,我怀疑这会有所帮助。在这一点上,我什至都不知道我打算做什么。
如果对发布什么代码有任何建议,请随时提问。 编辑:这是一个GenericViewModel。它不起作用,因为我不知道将“ Anything”设置为什么。编辑:添加了UserControl
public class GenericViewModel : Observable
{
//-Fields
private ObservableCollection<Anything> _items;
private Anything _selectedItem;
//-Properties
public ObservableCollection<Anything> Items
{
get { return _items; }
set { Set(ref _items, nameof(Items), value); }
}
public Anything SelectedItem
{
get { return _selectedItem; }
set { Set(ref _selectedItem, nameof(SelectedItem), value); }
}
//-Constructors
public GenericViewModel()
{
if (Items == null) Items = new ObservableCollection<Anything>();
}
//-Logic
public void MoveUp()
{
if (Items == null) return;
Helper.MoveItemUp(Items, _items.IndexOf(_selectedItem));
}
public void MoveDown()
{
if (Items == null) return;
Helper.MoveItemDown(Items, _items.IndexOf(_selectedItem));
}
public void Remove()
{
if (Items == null) return;
Helper.RemoveItem(Items, _items.IndexOf(_selectedItem));
}
}
UserControl
public partial class CustomListBox : UserControl
{
//-Fields
//-Properties
//-Dependencies
//-Constructor
public CustomListBox()
{
InitializeComponent();
}
//-Methods
private void ListboxButtonUp_Click(object sender, RoutedEventArgs e)
{
}
private void ListboxButtonDown_Click(object sender, RoutedEventArgs e)
{
}
private void ListboxButtonCopy_Click(object sender, RoutedEventArgs e)
{
}
private void ListboxButtonDelete_Click(object sender, RoutedEventArgs e)
{
}
private void BorderLayerThumbnail_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
}
private void BorderLayerThumbnail_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
}
}
<UserControl x:Class="BRRG_Scrubber.User_Controls.CustomListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BRRG_Scrubber"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="150">
<Grid Grid.Row="0" Margin="5,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" Grid.Row="0" FontSize="10" Foreground="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
<!--ItemsSource="{Binding Items}" SelectedItem="{Binding Current}"-->
<ListBox x:Name="listBoxPlus" Grid.Row="1" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" >
<ListBox.Resources>
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsFlicksEnabled" Value="True" />
<Style.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="14" />
<Setter Property="MinWidth" Value="14" />
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type local:Document}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Variable}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Layer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.30*" />
<ColumnDefinition Width="0.70*" />
</Grid.ColumnDefinitions>
<Border x:Name="borderLayerThumbnail" BorderBrush="#FF707070" BorderThickness="1" Width="50" Height="50" MouseRightButtonDown="BorderLayerThumbnail_MouseRightButtonDown" MouseLeftButtonDown="BorderLayerThumbnail_MouseLeftButtonDown" >
<Border.Background>
<ImageBrush ImageSource="/BRRG_Scrubber;component/Resources/Images/checkerboardtile.jpg" ViewportUnits="Absolute" Stretch="None" Viewport="0,0,12,12" TileMode="Tile"/>
</Border.Background>
<Image Grid.Column="0" Source="{Binding Image}" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" OpacityMask="Gray">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Opacity" Value="1.0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Visible}" Value="False">
<Setter Property="Opacity" Value="0.5"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="10,0,0,0">
<TextBox Text="{Binding Name}"/>
<TextBlock Text="{Binding Type, Mode=OneWay}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Opacity" Value="1.0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Visible}" Value="False">
<Setter Property="Opacity" Value="0.2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Opacity" Value="1.0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Locked}" Value="False">
<Setter Property="Opacity" Value="0.2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<WrapPanel Grid.Row="2" HorizontalAlignment="Right">
<WrapPanel.Resources>
<Style TargetType="Button">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="FontSize" Value="10" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="20" />
</Style>
</WrapPanel.Resources>
<Button x:Name="listboxButtonUp" Content="▲" Click="ListboxButtonUp_Click"/>
<Button x:Name="listboxButtonDown" Content="▼" Click="ListboxButtonDown_Click"/>
<Button x:Name="listboxButtonCopy" Content="⧉" Click="ListboxButtonCopy_Click"/>
<Button x:Name="listboxButtonDelete" Content="⛞" Click="ListboxButtonDelete_Click"/>
</WrapPanel>
</Grid>
</UserControl>
我真的很希望能够在UserControl中创建一个带有按钮的修改过的ListBox,该按钮可以上下移动项目并将其从列表中删除,我可以将其用于任何未知类型的ObservableCollection。我需要的ListBox的功能将完全相同,除了它们的Type在运行时之前是未知的。
编辑:埃德(Ed)建议的新密码
MainViewModel
public class MainViewModel : Observable
{
//-Fields
private Project _project;
private GenericViewModel<Document> _documentCollection;
private GenericViewModel<Variable> _variableCollection;
private GenericViewModel<Layer> _layerCollection;
//-Properties
public Project Project
{
get { return _project; }
set { Set(ref _project, nameof(Project), value); }
}
public GenericViewModel<Document> DocumentCollection
{
get { return _documentCollection; }
set { Set(ref _documentCollection, nameof(DocumentCollection), value); OnPropertyChanged(nameof(LayerCollection)); }
}
public GenericViewModel<Variable> VariableCollection
{
get { return _variableCollection; }
set { Set(ref _variableCollection, nameof(VariableCollection), value); }
}
public GenericViewModel<Layer> LayerCollection
{
get { return _layerCollection; }
set { Set(ref _layerCollection, nameof(LayerCollection), value); }
}
//-Constructors
public MainViewModel()
{
Project = new Project();
DocumentCollection = new GenericViewModel<Document>();
DocumentCollection.Items = Project.Documents;
}
//-Logic
}
带有绑定的测试窗口
<StackPanel>
<uc:CustomListBox DataContext="{Binding DocumentCollection}" Height="100"/>
<uc:CustomListBox DataContext="{Binding LayerCollection}" Height="200"/>
<ListBox ItemsSource="{Binding Project.Documents}" Height="100"/>
</StackPanel>
GenericViewModel
public class GenericViewModel<Anything> : Observable, ICollectionViewModel
{
//-Fields
private ObservableCollection<Anything> _items;
private Anything _selectedItem;
//-Properties
public ObservableCollection<Anything> Items
{
get { return _items; }
set { Set(ref _items, nameof(Items), value); }
}
public Anything SelectedItem
{
get { return _selectedItem; }
set { Set(ref _selectedItem, nameof(SelectedItem), value); }
}
//-Constructors
public GenericViewModel()
{
if (Items == null) Items = new ObservableCollection<Anything>();
}
//-Logic
...Removed For Brevity...
}
文档模型类
public class Document : Anything
{
//-Fields
private string _filePath = "New Document";
private ObservableCollection<Layer> _layers;
private ObservableCollection<Selection> _selections;
//-Properties
public string FilePath
{
get { return _filePath; }
set { Set(ref _filePath, nameof(FilePath), value); }
}
public ObservableCollection<Layer> Layers
{
get { return _layers; }
set { Set(ref _layers, nameof(Layers), value); }
}
//-Constructors
public Document()
{
if (Layers == null) Layers = new ObservableCollection<Layer>();
if (Selections == null) Selections = new ObservableCollection<Selection>();
}
public Document(string filepath)
{
this.FilePath = filepath;
if (Layers == null) Layers = new ObservableCollection<Layer>();
if (Selections == null) Selections = new ObservableCollection<Selection>();
Layers.Add(new Layer("LayerOne "+Name));
Layers.Add(new Layer("LayerTwo " + Name));
Layers.Add(new Layer("LayerThree " + Name));
Selections.Add(new Selection());
Selections.Add(new Selection());
}
//-Gets
public string Name
{
get { return Path.GetFileNameWithoutExtension(FilePath); }
}
}
答案 0 :(得分:0)
这里最大的问题似乎是您不能将类型参数未知的泛型转换为泛型,但是您希望viewmodel类是正确的泛型。该圆圈可以通过两种不同的有用方式平方,这两种方式都很有价值,因此我们将同时进行。
执行此操作的正确MVVM方法是为viewmodel提供一些调用这些方法的命令属性。 DelegateCommand类与RelayCommand类相同。如果您还没有互联网,互联网上就会有很多实现。
public ICommand MoveUpCommand { get; } =
new DelegateCommand(() => MoveUp());
XAML:
<Button Content="▲" Command="{Binding MoveUpCommand}" />
然后摆脱那些单击事件处理程序。您不需要他们。这非常巧妙地解决了您调用这些方法的问题。
但是,还有一种干净的方法可以从代码后面调用这些方法,这是了解是否要使用泛型的一种重要模式。
铸造问题的经典解决方案是框架用于泛型集合的一种解决方案:泛型IEnumerable<T>
实现非泛型System.Collections.IEnumerable
。 List<T>
实现了非通用的System.Collections.IList
。这些非通用接口以非通用方式提供相同的操作。您始终可以将List<T>
强制转换为非泛型IList
,并在不知道T
的情况下调用这些方法和属性。
任何精心设计的集合都可以分配给类型IEnumerable
的属性:例如,ListBox.ItemsSource
被声明为System.Collections.IEnumerable
。可以将任何集合分配给它,而ListBox无需知道集合中的类型。
因此,让我们编写一个非通用的接口,该接口公开我们需要访问的成员,而无需知道任何类型参数。
public interface ICollectionViewModel
{
void MoveUp();
void MoveDown();
void Remove();
}
如果这些方法原型之一包括收集项类型,例如void RemoveItem(Anything x)
,那将使事情复杂化,但是there's a classic solution to that problem as well。
您的Anything
已像类型参数一样被使用。我们需要做的就是将其声明为一个。您的方法已经具有实现接口方法的适当原型。
public class GenericViewModel<Anything> : Observable, ICollectionViewModel
像这样实例化:
this.DocumentCollection = new GenericViewModel<Document>();
现在,您的后台代码可以将GenericViewModel
的任何实例(无论类型参数如何)转换为支持所需操作的非通用接口:
private void ListboxButtonUp_Click(object sender, RoutedEventArgs e)
{
if (DataContext is ICollectionViewModel icollvm)
{
icollvm.MoveUp();
}
}