UserControl Listbox с общей коллекцией ObservableCollection, которая может быть изменена с помощью кнопок? - PullRequest
0 голосов
/ 08 мая 2019

Мне нужно иметь возможность отображать списки данных в ListBox с кнопками, которые могут перемещать элементы вверх и вниз и удалять элементы из ListBoxes и отражать это в моделях данных.

SampleDesign: http://bigriverrubber.com/_uploads/sites/2/usercontrollistbox.jpg

Я планирую иметь несколько списков ListBox, подобных этой, с одинаковыми функциями в нескольких окнах, поэтому я подумал, что мог бы создать UserControl со списком ListBox и нужными мне кнопками внутри, чтобы кнопки изменяли данные.Таким образом, я мог бы просто передать ObservableCollection в UserControl, и мне не пришлось бы каждый раз заново создавать кнопки.

Однако я обнаружил, что не могу перемещать элементы, если они связаныв коллекцию ObservableCollection, которой они должны быть для моих целей.Из того, что я прочитал, мне нужно вместо этого изменить коллекцию.

Но как мне это сделать из UserControl?Если тип ObservableCollection должен быть переменным, чтобы ListBox мог отображать множество типов списков, как я могу надеяться на него, чтобы получить доступ к методам Move и Remove в классе ObservableCollection?

I 'мы пытались взять ItemsSource, который был установлен в ObservableCollection, и преобразовать его в ObservableCollection , но это не сработало.

Я попытался привести его к типу ObservableCollection и ObservableCollection среди прочего, безрезультатно.

Я даже пытался реструктурировать мои ViewModel под GenericViewModel со свойством ObservableCollection , что привело к ошибке и оставило мой код в руинах, поэтому мне пришлось вернуться к резервной копии.

Я использовал ItemsControl, который изменяет ListBox в зависимости от того, какой тип DataType он находит, но это все равно будет означать, что я все равно должен делать отдельные события кнопки, так какой смысл?

Я бы опубликовалнекоторый код, но, видя, как ничего я не сделал, я работалЯ ни малейшего сомневаюсь, что это поможет любому.На данный момент я даже не знаю, можно ли вообще сделать то, что я собираюсь сделать.

Если есть какие-либо предложения по поводу того, какой код размещать, не стесняйтесь спрашивать.РЕДАКТИРОВАТЬ: Вот GenericViewModel.Это не работает, потому что я не знаю, на что установить «Все».РЕДАКТИРОВАТЬ: Добавлено 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>

Я действительно хотел бы иметь возможность создавать модифицированный ListBox в UserControl с кнопками, которые могут перемещать элементы вверх иВниз и удалите их из списка, который я могу использовать для любой ObservableCollection любого неизвестного типа.Все нужные мне ListBox будут функционировать одинаково, за исключением того, что их Тип будет неизвестен до времени выполнения.

РЕДАКТИРОВАТЬ: Новый код из предложений Эда

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 голосов
/ 08 мая 2019

Большая проблема здесь заключается в том, что вы не можете привести тип к универсальному классу с неизвестным параметром типа, но вы хотите, чтобы ваш класс viewmodel был правильно универсальным.Этот круг можно вычленить двумя разными полезными способами, оба ценными, чтобы знать, поэтому мы сделаем оба.

Правильный MVVM-способ сделать это - дать вашей viewmodel некоторые командные свойства, которые вызывают эти методы.Класс DelegateCommand совпадает с классом RelayCommand;Интернет полон реализаций, если у вас его еще нет.

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

XAML:

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

Затем избавьтесь от этих обработчиков событий щелчка.Вы не нуждаетесь в них.Это очень аккуратно решает вашу проблему вызова этих методов.

Тем не менее, есть также чистый способ вызова этих методов из кода, и это важный шаблон для изучения, если вы собираетесь работать с дженериками.

Классическим решением проблемы приведения является то, что фреймворк использует для родовых коллекций: Generic 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), это усложнило бы ситуацию, но есть и классическое решение этой проблемы .

Ваш Anything уже используется как параметр типа.Все, что нам нужно сделать, это объявить это как единое целое.У ваших методов уже есть соответствующие прототипы для реализации методов интерфейса.

public class GenericViewModel<Anything> : Observable, ICollectionViewModel

Создайте, например, так:

this.DocumentCollection = new GenericViewModel<Document>();

Теперь ваш codebehind может привести любой экземпляр GenericViewModel, независимо от параметра типа, к неуниверсальному интерфейсу, который поддерживает необходимые операции:

private void ListboxButtonUp_Click(object sender, RoutedEventArgs e)
{
    if (DataContext is ICollectionViewModel icollvm)
    {
        icollvm.MoveUp();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...