programing

ListBox 선택 지원 방법탐색 가능한 응용 프로그램에서 MVVM으로 바인딩된 항목

javajsp 2023. 5. 9. 22:06

ListBox 선택 지원 방법탐색 가능한 응용 프로그램에서 MVVM으로 바인딩된 항목

사용자 지정 "" 및 "disable" "disable" "disable" "disable" WPF를 )을할 수 .NavigationWindow에 . 한화서에나, ▁a▁..ListBox개의 해야 합니다.Extended 이 뷰 합니다. 이 화면에 대한 뷰 모델이 있으며 선택한 항목을 유지 관리해야 하므로 속성으로 저장합니다.

는 하만알, 나는고있다니습지▁the다니습▁that있▁aware▁am▁however고,알라는 것을 알고 있습니다.SelectedItemsListBox읽기 전용입니다.여기서 이 솔루션을 사용하여 문제를 해결하려고 노력했지만 구현에 채택할 수 없었습니다.하나 이상의 요소가 선택 취소된 경우와 화면 사이를 탐색하는 경우를 구분할 수 없음을 알게 되었습니다.NotifyCollectionChangedAction.Remove기술적으로 화면에서 멀어질 때 선택한 모든 항목이 선택 취소되므로 두 경우 모두 발생합니다.) 명령은뷰뷰뷰 뷰 모델에 수 .ListBox저 안에

저는 몇 가지 다른 덜 우아한 해결책을 찾았지만, 이 중 어떤 것도 뷰 모델과 뷰 사이의 양방향 바인딩을 강제하지는 않는 것 같습니다.

어떤 도움이라도 주시면 대단히 감사하겠습니다.내 문제를 이해하는 데 도움이 된다면 소스 코드의 일부를 제공할 수 있습니다.

작해보오시십을 만들어 .IsSelected 및 .ListBoxItem.IsSelected에.

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>

Rachel의 솔루션은 매우 효과적입니다!하지만 제가 직면한 한 가지 문제가 있습니다 - 만약 당신이 그 스타일을 무시한다면.ListBoxItem당신은 그것에 적용된 원래의 스타일링을 느슨하게 합니다(나의 경우 선택한 항목을 강조하는 등).원래 스타일에서 상속하면 이 문제를 방지할 수 있습니다.

<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>

설정 트노설정정설BasedOn( 답변 참조).

Rachel의 솔루션을 원하는 대로 사용할 수는 없었지만, 사용자 지정 종속성 속성을 생성하여 저에게 완벽하게 적용할 수 있다는 Sandesh의 답변을 얻었습니다.ListBox에 대해 비슷한 코드를 작성해야 했습니다.

public class ListBoxCustom : ListBox
{
    public ListBoxCustom()
    {
        SelectionChanged += ListBoxCustom_SelectionChanged;
    }

    void ListBoxCustom_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        SelectedItemsList = SelectedItems;
    }

    public IList SelectedItemsList
    {
        get { return (IList)GetValue(SelectedItemsListProperty); }
        set { SetValue(SelectedItemsListProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsListProperty =
       DependencyProperty.Register(nameof(SelectedItemsList), typeof(IList), typeof(ListBoxCustom), new PropertyMetadata(null));

}

View Model에서 선택한 목록을 가져오기 위해 해당 속성을 참조했습니다.

나는 이것에 대한 쉬운 해결책을 계속 찾았지만, 운이 없었습니다.

Rachel이 가지고 있는 솔루션은 항목 내의 개체에 Selected 속성이 이미 있는 경우에 좋습니다.원본. 그렇지 않은 경우 해당 비즈니스 모델에 대한 모델을 만들어야 합니다.

저는 다른 길로 갔습니다.빠르긴 하지만 완벽하지는 않습니다.

ListBox에서 SelectionChanged 이벤트를 만듭니다.

<ListBox ItemsSource="{Binding SomeItemsSource}"
         SelectionMode="Multiple"
         SelectionChanged="lstBox_OnSelectionChanged" />

이제 XAML 페이지 뒤의 코드에서 이벤트를 구현합니다.

private void lstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var listSelectedItems = ((ListBox) sender).SelectedItems;
    ViewModel.YourListThatNeedsBinding = listSelectedItems.Cast<ObjectType>().ToList();
}

타다. 됐습니다.

이 작업은 선택 항목 변환의 도움을 받아 수행되었습니다.항목 컬렉션을 목록에 추가합니다.

여기 또 다른 해결책이 있습니다.벤의 대답과 비슷하지만 바인딩은 두 가지 방식으로 작동합니다.요령은 다음을 업데이트하는 것입니다.ListBox바인딩된 데이터 항목이 변경될 때 선택한 항목입니다.

public class MultipleSelectionListBox : ListBox
{
    public static readonly DependencyProperty BindableSelectedItemsProperty =
        DependencyProperty.Register("BindableSelectedItems",
            typeof(IEnumerable<string>), typeof(MultipleSelectionListBox),
            new FrameworkPropertyMetadata(default(IEnumerable<string>),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));

    public IEnumerable<string> BindableSelectedItems
    {
        get => (IEnumerable<string>)GetValue(BindableSelectedItemsProperty);
        set => SetValue(BindableSelectedItemsProperty, value);
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        BindableSelectedItems = SelectedItems.Cast<string>();
    }

    private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MultipleSelectionListBox listBox)
            listBox.SetSelectedItems(listBox.BindableSelectedItems);
    }
}

유감스럽게도, 저는 사용할 수 없었습니다.IList Selected 합니다.항목 유형.그렇게 함으로써null유형이 다음인 내 뷰 모델의 속성으로.IEnumerable<string>.

XAML은 다음과 같습니다.

<v:MultipleSelectionListBox
    ItemsSource="{Binding AllMyItems}"
    BindableSelectedItems="{Binding MySelectedItems}"
    SelectionMode="Multiple"
    />

한 가지 주의할 점이 있습니다. 경우에는 제경우는에,는.ListBox보기에서 제거할 수 있습니다.어떤 이유로, 이것은 다음과 같은 원인이 됩니다.SelectedItems빈 목록으로 변경할 속성입니다.이렇게 하면 뷰 모델의 속성이 빈 목록으로 변경됩니다.사용 사례에 따라 이것은 바람직하지 않을 수 있습니다.

명령 및 상호 작용 이벤트 트리거를 사용하면 이 작업을 매우 쉽게 수행할 수 있습니다.ItemsCount는 업데이트된 개수를 표시하려는 경우 XAML에서 사용할 바인딩된 속성일 뿐입니다.

XAML:

     <ListBox ItemsSource="{Binding SomeItemsSource}"
                 SelectionMode="Multiple">
        <i:Interaction.Triggers>
         <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" 
                                   CommandParameter="{Binding ElementName=MyView, Path=SelectedItems.Count}" />
         </i:EventTrigger>
        </Interaction.Triggers>    
    </ListView>

<Label Content="{Binding ItemsCount}" />

모델 보기:

    private int _itemsCount;
    private RelayCommand<int> _selectionChangedCommand;

    public ICommand SelectionChangedCommand
    {
       get {
                return _selectionChangedCommand ?? (_selectionChangedCommand = 
             new RelayCommand<int>((itemsCount) => { ItemsCount = itemsCount; }));
           }
    }

        public int ItemsCount
        {
            get { return _itemsCount; }
            set { 
              _itemsCount = value;
              OnPropertyChanged("ItemsCount");
             }
        }

바인딩/선택 항목 사용을 구현하는 데 시간이 좀 걸렸습니다.제가 전문가가 아니기 때문에 누군가 유용하다고 생각한다면 제 솔루션을 공유하고 싶었습니다.Microsoft를 다운로드하는 것을 잊지 마십시오.자, 행동들.이 솔루션에 대한 Nuget의 Wpf.

선택한 WPF ListBox에 액세스하여 혜택을 받았습니다.항목

보기:

Window x:Class="WpfAppSelectedItems.MainWindow"
        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:i="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:local="clr-namespace:WpfAppSelectedItems"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Grid>
        <ListBox Height="250" Width="300"
            ItemsSource="{Binding Items}" SelectionMode="Extended"
            >
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                </Style>
            </ListBox.ItemContainerStyle>

            <ListBox.InputBindings>
                <KeyBinding Gesture="Ctrl+A" Command="{Binding SelectAllCommand}" />
            </ListBox.InputBindings>

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged" >
                    <i:CallMethodAction TargetObject="{Binding}" MethodName="ListBox_SelectionChanged"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListBox>

    </Grid>
</Window>

`

코드 배경:

namespace WpfAppSelectedItems
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel(); //connecting window to VM
        }
    }
}

모델 보기:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Xaml.Behaviors;
using System.Windows;

namespace WpfAppSelectedItems
{
    internal class ViewModel: Presenter
    {
        //Creating ItemPresenter class. IsSelected binded to Style in the view
        public class ItemPresenter : Presenter
        {
            private readonly string _value;

            public ItemPresenter(string value)
            {
                _value = value;
            }

            public override string ToString()
            {
                return _value;
            }

            private bool _isSelected;
            public bool IsSelected
            {
                get { return _isSelected; }
                set
                {
                    _isSelected = value;
                    OnPropertyChanged();
                }
            }
        }

        //Placing items to the Items which is binded to the ListBox 
        public ObservableCollection<ItemPresenter> Items { get; } = new ObservableCollection<ItemPresenter>
        {
            new ItemPresenter("A"),
            new ItemPresenter("B"),
            new ItemPresenter("C"),
            new ItemPresenter("D")
        };

        //Do something when selection changed including detecting SelectedItems
        public void ListBox_SelectionChanged()
        {
            foreach (var item in Items)
            {
                if (item.IsSelected)
                    MessageBox.Show(fufuitem.ToString());
                    
            }
        }
    };

    //Notify View if a property changes
    public abstract class Presenter : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

확인란을 IsSelected 속성에 바인딩하고 텍스트 블록과 확인란을 스택 패널에 배치하면 효과가 있습니다!

내가 혼자서 찾으려 했던 주어진 답에 만족하지 못한 채...글쎄요, 해결책이라기보다는 해킹에 가깝지만 저에게는 잘 작동합니다.이 솔루션은 특별한 방법으로 다중 바인딩을 사용합니다.처음에는 코드처럼 보이지만 아주 적은 노력으로 재사용할 수 있습니다.

먼저 'IMultiValue Converter'를 구현했습니다.

public class SelectedItemsMerger : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        SelectedItemsContainer sic = values[1] as SelectedItemsContainer;

        if (sic != null)
            sic.SelectedItems = values[0];

        return values[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new[] { value };
    }
}

그리고 선택됨품목 컨테이너/포장지:

public class SelectedItemsContainer
{
    /// Nothing special here...
    public object SelectedItems { get; set; }
}

이제 ListBox에 대한 바인딩을 만듭니다.선택된항목(단일).참고: 'Converter'에 대한 정적 리소스를 생성해야 합니다.이 작업은 응용 프로그램당 한 번씩 수행할 수 있으며 변환기가 필요한 모든 ListBox에 대해 재사용할 수 있습니다.

<ListBox.SelectedItem>
 <MultiBinding Converter="{StaticResource SelectedItemsMerger}">
  <Binding Mode="OneWay" RelativeSource="{RelativeSource Self}" Path="SelectedItems"/>
  <Binding Path="SelectionContainer"/>
 </MultiBinding>
</ListBox.SelectedItem>

View Model에서 바인딩할 수 있는 컨테이너를 만들었습니다.값으로 채우려면 new()로 초기화하는 것이 중요합니다.

    SelectedItemsContainer selectionContainer = new SelectedItemsContainer();
    public SelectedItemsContainer SelectionContainer
    {
        get { return this.selectionContainer; }
        set
        {
            if (this.selectionContainer != value)
            {
                this.selectionContainer = value;
                this.OnPropertyChanged("SelectionContainer");
            }
        }
    }

그리고 이것이 마지막입니다.누군가 개선된 점을 볼 수 있을까요?당신은 그것에 대해 어떻게 생각합니까?

이것은 저에게 중요한 문제였습니다. 제가 본 답변 중 일부는 너무 해킹적이거나 재설정이 필요했습니다.SelectedItems속성 값 - 속성 OnCollectionChanged 이벤트에 연결된 코드를 차단합니다.하지만 컬렉션을 직접 수정하고 보너스로 지원하는 솔루션을 얻을 수 있었습니다.SelectedValuePath개체 수집용입니다.

public class MultipleSelectionListBox : ListBox
{
    internal bool processSelectionChanges = false;

    public static readonly DependencyProperty BindableSelectedItemsProperty =
        DependencyProperty.Register("BindableSelectedItems",
            typeof(object), typeof(MultipleSelectionListBox),
            new FrameworkPropertyMetadata(default(ICollection<object>),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSelectedItemsChanged));

    public dynamic BindableSelectedItems
    {
        get => GetValue(BindableSelectedItemsProperty);
        set => SetValue(BindableSelectedItemsProperty, value);
    }


    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);

        if (BindableSelectedItems == null || !this.IsInitialized) return; //Handle pre initilized calls

        if (e.AddedItems.Count > 0)
            if (!string.IsNullOrWhiteSpace(SelectedValuePath))
            {
                foreach (var item in e.AddedItems)
                    if (!BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
                        BindableSelectedItems.Add((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
            }
            else
            {
                foreach (var item in e.AddedItems)
                    if (!BindableSelectedItems.Contains((dynamic)item))
                        BindableSelectedItems.Add((dynamic)item);
            }

        if (e.RemovedItems.Count > 0)
            if (!string.IsNullOrWhiteSpace(SelectedValuePath))
            {
                foreach (var item in e.RemovedItems)
                    if (BindableSelectedItems.Contains((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null)))
                        BindableSelectedItems.Remove((dynamic)item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
            }
            else
            {
                foreach (var item in e.RemovedItems)
                    if (BindableSelectedItems.Contains((dynamic)item))
                        BindableSelectedItems.Remove((dynamic)item);
            }
    }

    private static void OnBindableSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MultipleSelectionListBox listBox)
        {
            List<dynamic> newSelection = new List<dynamic>();
            if (!string.IsNullOrWhiteSpace(listBox.SelectedValuePath))
                foreach (var item in listBox.BindableSelectedItems)
                {
                    foreach (var lbItem in listBox.Items)
                    {
                        var lbItemValue = lbItem.GetType().GetProperty(listBox.SelectedValuePath).GetValue(lbItem, null);
                        if ((dynamic)lbItemValue == (dynamic)item)
                            newSelection.Add(lbItem);
                    }
                }
            else
                newSelection = listBox.BindableSelectedItems as List<dynamic>;

            listBox.SetSelectedItems(newSelection);
        }
    }
}

바인딩은 MS가 직접 수행했을 것으로 예상한 대로 작동합니다.

<uc:MultipleSelectionListBox 
    ItemsSource="{Binding Items}" 
    SelectionMode="Extended" 
    SelectedValuePath="id" 
    BindableSelectedItems="{Binding mySelection}"
/>

이 제품은 철저한 테스트를 거치지는 않았지만, 일별 검사를 통과했습니다.저는 컬렉션에 동적인 유형을 사용하여 재사용할 수 있도록 유지하려고 노력했습니다.

선택한 요소의 이름만 가져오려면 다음을 수행할 수 있습니다.

보기:

<ListBox
    x:Name="Folders"
    Grid.Row="1"
    Grid.Column="0"
    ItemsSource="{Binding YourListWithStings}"
    SelectionMode="Single"
    SelectedItem="{Binding ToYourOutputVariable}"
    >
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

모델 보기:

private string _ToYourOutputVariable
public string ToYourOutputVariable
{
    get {return _ToYourOutputVariable; }
    set 
       {
          _ToYourOutputVariable = value;
          NotifyOfPropertyChange(); 
          MessageBox.Show(_ToYourOutputVariable);
       }
}

메시지 상자에 선택한 목록 항목의 이름이 표시됩니다.메시지 상자를 여는 함수를 호출할 수 있습니다.

언급URL : https://stackoverflow.com/questions/11142976/how-to-support-listbox-selecteditems-binding-with-mvvm-in-a-navigable-applicatio