具有可通过按钮修改的通用ObservableCollection的UserControl列表框?

时间:2019-05-07 21:08:18

标签: c# wpf mvvm

我需要能够在带按钮的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 ,但无济于事。

我什至尝试过在具有ObservableCollection 属性的GenericViewModel下重构ViewModel,但失败了,代码也一团糟,所以我不得不返回备份。

我使用了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); }
        }
    }

1 个答案:

答案 0 :(得分:0)

这里最大的问题似乎是您不能将类型参数未知的泛型转换为泛型,但是您希望viewmodel类是正确的泛型。该圆圈可以通过两种不同的有用方式平方,这两种方式都很有价值,因此我们将同时进行。

执行此操作的正确MVVM方法是为viewmodel提供一些调用这些方法的命令属性。 DelegateCommand类与RelayCommand类相同。如果您还没有互联网,互联网上就会有很多实现。

public ICommand MoveUpCommand { get; } = 
    new DelegateCommand(() => MoveUp());

XAML:

<Button Content="▲" Command="{Binding MoveUpCommand}" />

然后摆脱那些单击事件处理程序。您不需要他们。这非常巧妙地解决了您调用这些方法的问题。

但是,还有一种干净的方法可以从代码后面调用这些方法,这是了解是否要使用泛型的一种重要模式。

铸造问题的经典解决方案是框架用于泛型集合的一种解决方案:泛型IEnumerable<T>实现非泛型System.Collections.IEnumerableList<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();
    }
}