41

カスタムの[次へ]および[戻る]ボタンとコマンドを使用してナビゲートできる(つまり、を使用しないNavigationWindow)WPFアプリケーションを作成しています。1つの画面に、 (モードListBoxを使用して)複数の選択をサポートする必要があるがあります。Extendedこの画面のビューモデルがあり、選択したアイテムをプロパティとして保存します。これは、それらを維持する必要があるためです。

ただし、SelectedItemsaのプロパティListBoxは読み取り専用であることを認識しています。私はここでこのソリューションを使用して問題を回避しようとしていますが、実装に採用することができませんでした。1つ以上の要素が選択解除されたときと、画面間を移動したときを区別できないことがわかりました(画面から移動するとNotifyCollectionChangedAction.Remove、技術的には選択されたすべての項目が選択解除されるため、どちらの場合も発生します)。私のナビゲーションコマンドは、各画面のビューモデルを管理する個別のビューモデルに配置されているため、ListBoxそこにビューモデルに関連する実装を配置することはできません。

私は他のいくつかのあまりエレガントでない解決策を見つけましたが、これらのどれもビューモデルとビューの間の双方向のバインドを強制するようには見えません。

どんな助けでも大歓迎です。問題を理解するのに役立つ場合は、ソースコードの一部を提供できます。

4

9 に答える 9

57

IsSelected各データ項目にプロパティを作成し、ListBoxItem.IsSelectedそのプロパティにバインドしてみてください

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
于 2012-06-21T17:07:18.493 に答える
20

レイチェルのソリューションはうまく機能します!しかし、私が遭遇した問題が1つあります。スタイルをオーバーライドするとListBoxItem、適用されていた元のスタイルが失われます(私の場合、選択したアイテムの強調表示などを担当します)。元のスタイルから継承することで、これを回避できます。

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

設定に注意してください(この回答BasedOnを参照してください)。

于 2014-01-21T15:55:23.813 に答える
13

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));

}

ビューモデルでは、選択したリストを取得するためにそのプロパティを参照しました。

于 2016-01-18T20:49:17.613 に答える
6

私はこれに対する簡単な解決策を探し続けましたが、運がありませんでした。

ItemsSource内のオブジェクトに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();
}

多田。終わり。

これは、SelectedItemCollectionをリストに変換することで行われました。

于 2017-09-25T13:38:35.550 に答える
3

ここにさらに別の解決策があります。ベンの答えに似ていますが、バインディングは2つの方法で機能します。秘訣は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);
    }
}

残念ながら、IListBindableSelectedItemsタイプとして使用できませんでした。そうすることnullで、タイプが。であるビューモデルのプロパティに送信されますIEnumerable<string>

XAMLは次のとおりです。

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

注意すべきことが1つあります。私の場合、aListBoxはビューから削除される可能性があります。何らかの理由で、これによりSelectedItemsプロパティが空のリストに変更されます。これにより、ビューモデルのプロパティが空のリストに変更されます。ユースケースによっては、これは望ましくない場合があります。

于 2018-07-09T22:39:45.210 に答える
2

これは、コマンドとInteractivitiesEventTriggerを使用して行うのは非常に簡単でした。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}" />

ViewModel:

    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");
             }
        }
于 2018-11-27T21:52:12.500 に答える
0

チェックボックスをIsSelectedプロパティにバインドし、テキストブロックとチェックボックスをスタックパネル内に配置するとうまくいきます。

于 2017-04-21T17:46:44.633 に答える
0

私が自分で見つけようとしていた与えられた答えに満足していません...まあ、それは解決策というよりはハックのようなものであることがわかりましたが、私にとってはうまくいきます。このソリューションは、特別な方法でマルチバインディングを使用します。最初は大量のコードのように見えるかもしれませんが、ごくわずかな労力で再利用できます。

最初に「IMultiValueConverter」を実装しました

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 };
    }
}

そしてSelectedItemsコンテナ/ラッパー:

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

次に、ListBox.SelectedItem(Singular)のバインディングを作成します。注:「コンバーター」の静的リソースを作成する必要があります。これは、アプリケーションごとに1回実行し、コンバーターを必要とするすべてのリストボックスで再利用できます。

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

ViewModelで、バインドできるコンテナを作成しました。値を入力するには、new()で初期化することが重要です。

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

以上です。多分誰かがいくつかの改善を見ていますか?あなたはそれについてどう思いますか?

于 2017-09-26T08:40:44.383 に答える
0

これは私にとって大きな問題でした。私が見た回答のいくつかは、ハックすぎるか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}"
/>

徹底的なテストはされていませんが、一見検査に合格しています。コレクションに動的型を採用することで、再利用可能にしようとしました。

于 2018-08-13T23:06:08.230 に答える