トップ回答者
複数のListBoxを内包するコントロール(ListBox継承)を作成したい

質問
-
複数のListBoxを内包した、ユーザーコントロール(ListBox継承)を作成したいと思っています。
外部から見えるのは「ユーザーコントロール(ListBox継承)」で、使い方も標準のListBoxと同じとしたいです。
・内包するListBox同士で選択状態は排他的
・選択状態は、外部から見える「ユーザーコントロール(ListBox継承)」で取得可能。
・イベントも通常のListBoxと同じように利用可能(SelectedItemChangedなど)
・内包するListBoxの幅や高さはPanelを用いて動的に変更
イメージとしては以下のような形です。
どのようにするとうまく実現できるのでしょうか?
ご助言をいただけますと幸いです。よろしくお願いいたします。
- 編集済み yj0529 2018年6月7日 5:56
回答
-
このユーザーコントロール自体はリストボックスの機能は持たない"ガワ"なので、ListBoxを継承するというより、使いたいコマンドやプロパティを自前で定義して、内部の二つのリストボックスと連携するのが良いと思います。
↓は、二つのリストボックス共通のItemsSourceプロパティを持ち、どちらか一方のListBoxでSelectionChangedが発生したら、UserControl の親にSelectionChangedイベントを発行するUserControlのサンプルです
二つのリストボックスのItemsSourceは、UserControlが持つItemsSourceプロパティをBindingしてます。
また、それぞれのリストボックスで発生したSelectionChangedイベントをUserControlが拾って、親に対してSelectionChangedイベントを発行しています。
- TwinListBoxUC.xaml
<UserControl x:Class="TwinListBox.TwinListBoxUC" 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:TwinListBox"> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <ListBox x:Name="ListBoxA" ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TwinListBoxUC}}}" SelectionChanged="ListBoxA_SelectionChanged" Margin="5" /> <ListBox x:Name="ListBoxB" ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TwinListBoxUC}}}" SelectionChanged="ListBoxB_SelectionChanged" Margin="5" Grid.Column="1" /> </Grid> </UserControl>
- TwinListBoxUC.xaml.CS
using System.Collections; using System.Windows; using System.Windows.Controls; namespace TwinListBox { public partial class TwinListBoxUC : UserControl { public TwinListBoxUC() { InitializeComponent(); } // リストボックスに表示する共通ItemsSource public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IList), typeof(TwinListBoxUC), new FrameworkPropertyMetadata(null)); public IList ItemsSource { get { return (IList)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } // どちらかのリストボックスで発生したSelectedItemChangedEvent を通知するルーティングイベント public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedItemChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(TwinListBoxUC)); public event SelectionChangedEventHandler SelectedItemChanged { add { this.AddHandler(SelectedItemChangedEvent, value); } remove { this.RemoveHandler(SelectedItemChangedEvent, value); } } void RaiseSelectionChangedEvent(object sender, SelectionChangedEventArgs e) { var newEventArgs = new SelectionChangedEventArgs(TwinListBoxUC.SelectedItemChangedEvent, e.RemovedItems, e.AddedItems); RaiseEvent(newEventArgs); } // リストボックスAのSelectionChangedイベントハンドラ private void ListBoxA_SelectionChanged(object sender, SelectionChangedEventArgs e) { RaiseSelectionChangedEvent(sender, e); } // リストボックスBのSelectionChangedイベントハンドラ private void ListBoxB_SelectionChanged(object sender, SelectionChangedEventArgs e) { RaiseSelectionChangedEvent(sender, e); } } }
このコントロールを使う側は下記のようになります
- MainWindow.xaml
<Window x:Class="TwinListBox.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TwinListBox" Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded"> <local:TwinListBoxUC ItemsSource="{Binding items}" SelectedItemChanged="TwinListBoxUC_SelectedItemChanged"/> </Window>
MainWindow.xaml.cs
using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; namespace TwinListBox { public partial class MainWindow : Window { public ListboxData data = new ListboxData(); public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { data.items.Add("あ"); data.items.Add("い"); data.items.Add("う"); this.DataContext = data; } private void TwinListBoxUC_SelectedItemChanged(object sender, SelectionChangedEventArgs e) { MessageBox.Show(e.AddedItems[0].ToString()); } } public class ListboxData : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private static readonly PropertyChangedEventArgs ItemsPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(items)); private List<string> _items = new List<string>(); public List<string> items { get { return _items; } set { if (_items == value) { return; } _items = value; PropertyChanged?.Invoke(this, ItemsPropertyChangedEventArgs); } } } }
- TwinListBoxUC.xaml
すべての返信
-
このユーザーコントロール自体はリストボックスの機能は持たない"ガワ"なので、ListBoxを継承するというより、使いたいコマンドやプロパティを自前で定義して、内部の二つのリストボックスと連携するのが良いと思います。
↓は、二つのリストボックス共通のItemsSourceプロパティを持ち、どちらか一方のListBoxでSelectionChangedが発生したら、UserControl の親にSelectionChangedイベントを発行するUserControlのサンプルです
二つのリストボックスのItemsSourceは、UserControlが持つItemsSourceプロパティをBindingしてます。
また、それぞれのリストボックスで発生したSelectionChangedイベントをUserControlが拾って、親に対してSelectionChangedイベントを発行しています。
- TwinListBoxUC.xaml
<UserControl x:Class="TwinListBox.TwinListBoxUC" 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:TwinListBox"> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <ListBox x:Name="ListBoxA" ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TwinListBoxUC}}}" SelectionChanged="ListBoxA_SelectionChanged" Margin="5" /> <ListBox x:Name="ListBoxB" ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TwinListBoxUC}}}" SelectionChanged="ListBoxB_SelectionChanged" Margin="5" Grid.Column="1" /> </Grid> </UserControl>
- TwinListBoxUC.xaml.CS
using System.Collections; using System.Windows; using System.Windows.Controls; namespace TwinListBox { public partial class TwinListBoxUC : UserControl { public TwinListBoxUC() { InitializeComponent(); } // リストボックスに表示する共通ItemsSource public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IList), typeof(TwinListBoxUC), new FrameworkPropertyMetadata(null)); public IList ItemsSource { get { return (IList)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } // どちらかのリストボックスで発生したSelectedItemChangedEvent を通知するルーティングイベント public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedItemChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(TwinListBoxUC)); public event SelectionChangedEventHandler SelectedItemChanged { add { this.AddHandler(SelectedItemChangedEvent, value); } remove { this.RemoveHandler(SelectedItemChangedEvent, value); } } void RaiseSelectionChangedEvent(object sender, SelectionChangedEventArgs e) { var newEventArgs = new SelectionChangedEventArgs(TwinListBoxUC.SelectedItemChangedEvent, e.RemovedItems, e.AddedItems); RaiseEvent(newEventArgs); } // リストボックスAのSelectionChangedイベントハンドラ private void ListBoxA_SelectionChanged(object sender, SelectionChangedEventArgs e) { RaiseSelectionChangedEvent(sender, e); } // リストボックスBのSelectionChangedイベントハンドラ private void ListBoxB_SelectionChanged(object sender, SelectionChangedEventArgs e) { RaiseSelectionChangedEvent(sender, e); } } }
このコントロールを使う側は下記のようになります
- MainWindow.xaml
<Window x:Class="TwinListBox.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TwinListBox" Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded"> <local:TwinListBoxUC ItemsSource="{Binding items}" SelectedItemChanged="TwinListBoxUC_SelectedItemChanged"/> </Window>
MainWindow.xaml.cs
using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; namespace TwinListBox { public partial class MainWindow : Window { public ListboxData data = new ListboxData(); public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { data.items.Add("あ"); data.items.Add("い"); data.items.Add("う"); this.DataContext = data; } private void TwinListBoxUC_SelectedItemChanged(object sender, SelectionChangedEventArgs e) { MessageBox.Show(e.AddedItems[0].ToString()); } } public class ListboxData : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private static readonly PropertyChangedEventArgs ItemsPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(items)); private List<string> _items = new List<string>(); public List<string> items { get { return _items; } set { if (_items == value) { return; } _items = value; PropertyChanged?.Invoke(this, ItemsPropertyChangedEventArgs); } } } }
- TwinListBoxUC.xaml