none
複数のListBoxを内包するコントロール(ListBox継承)を作成したい RRS feed

  • 質問

  • 複数のListBoxを内包した、ユーザーコントロール(ListBox継承)を作成したいと思っています。

    外部から見えるのは「ユーザーコントロール(ListBox継承)」で、使い方も標準のListBoxと同じとしたいです。

    ・内包するListBox同士で選択状態は排他的

    ・選択状態は、外部から見える「ユーザーコントロール(ListBox継承)」で取得可能。

    ・イベントも通常のListBoxと同じように利用可能(SelectedItemChangedなど)

    ・内包するListBoxの幅や高さはPanelを用いて動的に変更

    イメージとしては以下のような形です。


    どのようにするとうまく実現できるのでしょうか?

    ご助言をいただけますと幸いです。よろしくお願いいたします。


    • 編集済み yj0529 2018年6月7日 5:56
    2018年6月6日 9:37

回答

  • このユーザーコントロール自体はリストボックスの機能は持たない"ガワ"なので、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);
    			}
    		}
    	}
    
    }


    • 編集済み NIM5 2018年6月7日 6:11
    • 回答としてマーク yj0529 2018年6月7日 8:36
    2018年6月7日 6:08

すべての返信

  • このユーザーコントロール自体はリストボックスの機能は持たない"ガワ"なので、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);
    			}
    		}
    	}
    
    }


    • 編集済み NIM5 2018年6月7日 6:11
    • 回答としてマーク yj0529 2018年6月7日 8:36
    2018年6月7日 6:08
  • とても参考になります。ありがとうございました。
    2018年6月7日 8:37