none
WPF DataGrid上の ComboBox に初期値を設定する(表示値を制限、切り替えする) RRS feed

  • 質問


  • DataGrid の質問になって申し訳ないのですが、
    DataGrid の ComboBox にキーボードでフォーカスをあわせたときの挙動について質問です。
    スタイルで非表示にした項目が、 ↑ ↓ のキー入力で選択できてしまい困っています。

    ComboBoxのふるまいは、Enumのデータを格納し次のデータ表現しています。
    ・データ1(初期値 & コンボボックスの非表示項目)
    ・データ2
    ・データ3
    ・データ4

    このうちデータ1は初期値(未選択など)で、 ComboBox に表示したくありません。
    初期値は null 値以外で対応したいのですが、そうすると表示項目に表示したくないデータ1が表示されてしまいます。

    まず、マウスでコンボボックスを開いた時は、StyleにTriggerを定義することで表示からデータ1を見えない状態にできました。
    しかし、DataGrid の ComboBox にキーボードでフォーカスをあわせたとき、スタイルで非表示にした項目が、 ↑ ↓ のキー入力で選択できてしまいます。(DataGridとは無関係に、通常のふるまいでもそうかもしれません)

    つぎに、 Converter クラスで ItemSource 自体を2,3,4データだけの表示にフィルターをすると、項目の制限はできたのですが、
    今度はデータ1をテーブルの列データがデータ1を持っているときに、 ComboBox は 空白状態になってしまい、それはそれで好ましくない状態です。
    (このとき TargetNullValue で切り替えしてくれないかと思いましたが、やはり null ではないので切り替わりませんでした)

    最後に、上の組み合わせで表示値自体を下記の Converter で切り替えようとしたのですが、データ1がコンバーターに送られてきませんでした。

    <ComboBox.Style>
    	<Style TargetType="{x:Type ComboBox}">
    
    		<Setter Property="ItemTemplate">
    			<Setter.Value>
    				<DataTemplate>
    					<ContentPresenter Content="{Binding ., Mode=OneWay, Converter={StaticResource ComboBoxTextConverter}}" />
    				</DataTemplate>
    			</Setter.Value>
    		</Setter>
    	</Style>
    </ComboBox.Style>
    
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
    	var data = (SampleEnum)value;
    	var text = "";
    
    	if (data == SampleEnum.DISABLED_DATA1 || data == SampleEnum.DISABLED_DATA2)
    	{
    		text = "初期値";
    	}
    	else
    	{
    		text = data.ToString() + "(DECORATE)";
    	}
    
    	return text;
    }

    これは ComboBox では表現できない動きか、仕様上よくない設定なのでしょうか?
    なにかわかるかたいましたら、お手数おかけしますが、教えていただけないでしょうか。
    よろしくお願いします。


    開発環境
    Window 1o pro x64
    Visual Studio 2015 Update3

    テスト環境のデータ
    https://1drv.ms/u/s!AmGCTVXH7L6MtCfS8JajKXp-YX3l

    2017年3月9日 6:21

回答

  • PreviewKeyDownで上下キーを捕捉して、選択許可する項目になるようにインデックスを補正してみる

    <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApplication1" 
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <app:SampleEnumSelectableConverter x:Key="sampleConv" />
        </Window.Resources>
        <StackPanel>
            <DataGrid x:Name="dataGrid1" ItemsSource="{Binding}" CanUserAddRows="False" AutoGenerateColumns="true">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Width="200">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=Sample}" IsHitTestVisible="False"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                        <DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <!-- Cellの値からVMを作って操作 -->
                                <ComboBox DataContext="{Binding Mode=OneTime,Path=Sample,Converter={StaticResource sampleConv}}" 
                                          ItemsSource="{Binding Path=Values}" 
                                          DisplayMemberPath="Value" 
                                          SelectedValuePath="Value" 
                                          SelectedValue="{Binding Path=DataContext.Sample,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContentPresenter}}}" 
                                          app:ComboBoxFilterTool.EnableFilter="true">
                                    <ComboBox.ItemContainerStyle>
                                        <Style TargetType="ComboBoxItem">
                                            <Style.Triggers>
                                                <DataTrigger Binding="{Binding Path=CanSelect}" Value="false">
                                                    <Setter Property="Visibility" Value="Collapsed" />
                                                </DataTrigger>
                                            </Style.Triggers>
                                        </Style>
                                    </ComboBox.ItemContainerStyle>
                                </ComboBox>
    
                            </DataTemplate>
                        </DataGridTemplateColumn.CellEditingTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
    
        </StackPanel>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.dataGrid1.ItemsSource = new TestModel().Rows;
            }
        }
    
        /// <summary>DataGridRowに表示する1行のデータ</summary>
        public class Row
        {
            public int ID { get; set; }
            public SampleEnum Sample { get; set; }
        }
        public class TestModel
        {
            public TestModel()
            {
                Rows = new List<Row>();
                Rows.Add(new Row() { ID = 1, Sample = SampleEnum.DISABLED_DATA1 });
                Rows.Add(new Row() { ID = 2, Sample = SampleEnum.DISABLED_DATA2 });
            }
            /// <summary>DataGridに表示する一覧</summary>
            public List<Row> Rows { get; set; }
        }
    
        [System.ComponentModel.TypeConverterAttribute(typeof(SampleEnumTypeConverter))]
        public enum SampleEnum
        {
            DATA1, DATA2, DISABLED_DATA1, DATA3, DISABLED_DATA2, DATA4
        }
        class SampleEnumTypeConverter : System.ComponentModel.TypeConverter
        {
            public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                var s=(SampleEnum)value;
                if (destinationType == typeof(string))
                {
                    if (s == SampleEnum.DISABLED_DATA1 || s == SampleEnum.DISABLED_DATA2)
                    {
                        return "初期値";
                    }
                    else
                    {
                        return s.ToString() + "(DECORATE)";
                    }
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    
        class SampleEnumSelectableConverter : SelectableConverter<SampleEnum>
        {
            public SampleEnumSelectableConverter()
            {
                this.RequestFilter += SampleEnumSelectableConverter_RequestFilter;
            }
    
            void SampleEnumSelectableConverter_RequestFilter(object sender, SelectableConverterEventArgs e)
            {
                e.Filter = (v) =>
                    {
                        return (SampleEnum)v != SampleEnum.DISABLED_DATA1 && (SampleEnum)v != SampleEnum.DISABLED_DATA2;
                    };
                e.Source = Enum.GetValues(typeof(SampleEnum));
            }
        }
    
    
        /**********************************************************/
    
        class ComboBoxFilterTool
        {
            /// <summary>ComboBoxでキーの上下で選択可能な項目の処理をする</summary>
            private static void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
            {
                ComboBox combo = (ComboBox)sender;
                if ((Keyboard.Modifiers & ModifierKeys.None) != ModifierKeys.Alt && !combo.IsDropDownOpen)
                {
                    int index = combo.SelectedIndex;
                    int def = 0;
                    switch (e.Key)
                    {
                        case Key.Down:
                            def = 1;
                            break;
                        case Key.Up:
                            def = -1;
                            break;
                        default:
                            return;
                    }
    
                    ISelectable selectable;
                    int newindex = index;
                    do
                    {
                        newindex = newindex + def;
                        if (newindex < 0 || combo.Items.Count <= newindex)
                        {
                            newindex = index;
                            break;
                        }
                        //選択可能な項目か調べる
                        selectable = combo.Items[newindex] as ISelectable;
                    } while (selectable == null || !selectable.CanSelect);
    
                    //選択可能な項目を選択
                    combo.SelectedIndex = newindex;
                    e.Handled = true;
                }
    
            }
    
            #region フィルタ処理を有効/無効の添付ビヘイビア
    
            public static bool GetEnableFilter(DependencyObject obj) { return (bool)obj.GetValue(EnableFilterProperty); }
            public static void SetEnableFilter(DependencyObject obj, bool value) { obj.SetValue(EnableFilterProperty, value); }
            public static readonly DependencyProperty EnableFilterProperty = DependencyProperty.RegisterAttached("EnableFilter", typeof(bool), typeof(ComboBoxFilterTool), new PropertyMetadata(default(bool), new PropertyChangedCallback(OnEnableFilterPropertyChanged)));
            private static void OnEnableFilterPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                ComboBox target = dpo as ComboBox;
                if (target != null)
                {
                    bool newValue = (bool)e.NewValue;
                    bool oldValue = (bool)e.OldValue;
    
                    target.PreviewKeyDown -= ComboBox_PreviewKeyDown;
                    if (newValue)
                    {
                        target.PreviewKeyDown += ComboBox_PreviewKeyDown;
                        //target.SetBinding(ComboBox.ItemsSourceProperty, new Binding("Values"));
                        //target.DisplayMemberPath = "Value";
                        //target.SelectedValuePath = "Value";
                    }
                }
            }
    
            #endregion
        }
    
        public class SelectableConverterEventArgs
        {
            public SelectableConverterEventArgs(object customData)
            {
                this.CustomData = customData;
            }
            /// <summary>フィルタ関数</summary>
            public Func<object, bool> Filter { get; set; }
    
            /// <summary>フィルタ処理前の一覧</summary>
            public System.Collections.IEnumerable Source { get; set; }
    
            public object CustomData { get; private set; }
        }
    
        /// <summary>一覧から選択できるようにするコンバーター</summary>
        abstract class SelectableConverterBase : IValueConverter
        {
    
            public event EventHandler<SelectableConverterEventArgs> RequestFilter;
    
            protected SelectableConverterEventArgs GetFilter(object customData)
            {
                var rf = RequestFilter;
                if (rf != null)
                {
                    var e = new SelectableConverterEventArgs(customData);
                    rf(this, e);
                    return e;
                }
                return null;
            }
    
            public abstract object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    
        /// <summary>特定の型の一覧から選択できるようにするコンバーター</summary>
        /// <typeparam name="T"></typeparam>
        class SelectableConverter<T> : SelectableConverterBase
        {
            public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value is T)
                {
                    var e = GetFilter(parameter);
                    if (e != null)
                    {
                        return new ItemViewModel<T>(value, e.Source as IEnumerable<T>, e.Filter) { Value = (T)value };
                    }
                }
                return null;
            }
    
        }
    
        /// <summary>DataGridCellで表示する内容のVM</summary>
        /// <typeparam name="T"></typeparam>
        class ItemViewModel<T>
        {
            public ItemViewModel(object original, IEnumerable<T> source, Func<object, bool> filter)
            {
                OriginalSource = original;
                if (source != null)
                {
                    Values = source.Select(v => new SelectableItem<T>(v, filter(v))).ToList();
                }
            }
            /// </summary>元の値</summary>
            public object OriginalSource { get; private set; }
            /// <summary>ComboBoxの選択値</summary>
            public T Value { get; set; }
    
            /// <summary>ComboBoxに表示する一覧</summary>
            public List<SelectableItem<T>> Values { get; set; }
        }
    
        /// <summary>一覧の項目に選択可能かどうかのフラグを持たせるVM</summary>
        /// <remarks>ComboBoxの一覧で表示するかどうか</remarks>
        class SelectableItem<T> : ISelectable, System.ComponentModel.INotifyPropertyChanged
        {
            public SelectableItem(T value, bool canSelect = true) { Value = value; CanSelect = canSelect; }
            public T Value { get; private set; }
            public bool CanSelect { get; set; }
    
            public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                var pc = PropertyChanged;
                if (pc != null) { pc(this, new System.ComponentModel.PropertyChangedEventArgs(name)); }
            }
        }
    
        /// <summary>選択可能かどうかのフラグを持っているインターフェース</summary>
        interface ISelectable
        {
            bool CanSelect { get; }
        }
    }



    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年3月9日 16:54

すべての返信

  • PreviewKeyDownで上下キーを捕捉して、選択許可する項目になるようにインデックスを補正してみる

    <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApplication1" 
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <app:SampleEnumSelectableConverter x:Key="sampleConv" />
        </Window.Resources>
        <StackPanel>
            <DataGrid x:Name="dataGrid1" ItemsSource="{Binding}" CanUserAddRows="False" AutoGenerateColumns="true">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Width="200">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=Sample}" IsHitTestVisible="False"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                        <DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <!-- Cellの値からVMを作って操作 -->
                                <ComboBox DataContext="{Binding Mode=OneTime,Path=Sample,Converter={StaticResource sampleConv}}" 
                                          ItemsSource="{Binding Path=Values}" 
                                          DisplayMemberPath="Value" 
                                          SelectedValuePath="Value" 
                                          SelectedValue="{Binding Path=DataContext.Sample,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContentPresenter}}}" 
                                          app:ComboBoxFilterTool.EnableFilter="true">
                                    <ComboBox.ItemContainerStyle>
                                        <Style TargetType="ComboBoxItem">
                                            <Style.Triggers>
                                                <DataTrigger Binding="{Binding Path=CanSelect}" Value="false">
                                                    <Setter Property="Visibility" Value="Collapsed" />
                                                </DataTrigger>
                                            </Style.Triggers>
                                        </Style>
                                    </ComboBox.ItemContainerStyle>
                                </ComboBox>
    
                            </DataTemplate>
                        </DataGridTemplateColumn.CellEditingTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
    
        </StackPanel>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.dataGrid1.ItemsSource = new TestModel().Rows;
            }
        }
    
        /// <summary>DataGridRowに表示する1行のデータ</summary>
        public class Row
        {
            public int ID { get; set; }
            public SampleEnum Sample { get; set; }
        }
        public class TestModel
        {
            public TestModel()
            {
                Rows = new List<Row>();
                Rows.Add(new Row() { ID = 1, Sample = SampleEnum.DISABLED_DATA1 });
                Rows.Add(new Row() { ID = 2, Sample = SampleEnum.DISABLED_DATA2 });
            }
            /// <summary>DataGridに表示する一覧</summary>
            public List<Row> Rows { get; set; }
        }
    
        [System.ComponentModel.TypeConverterAttribute(typeof(SampleEnumTypeConverter))]
        public enum SampleEnum
        {
            DATA1, DATA2, DISABLED_DATA1, DATA3, DISABLED_DATA2, DATA4
        }
        class SampleEnumTypeConverter : System.ComponentModel.TypeConverter
        {
            public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                var s=(SampleEnum)value;
                if (destinationType == typeof(string))
                {
                    if (s == SampleEnum.DISABLED_DATA1 || s == SampleEnum.DISABLED_DATA2)
                    {
                        return "初期値";
                    }
                    else
                    {
                        return s.ToString() + "(DECORATE)";
                    }
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    
        class SampleEnumSelectableConverter : SelectableConverter<SampleEnum>
        {
            public SampleEnumSelectableConverter()
            {
                this.RequestFilter += SampleEnumSelectableConverter_RequestFilter;
            }
    
            void SampleEnumSelectableConverter_RequestFilter(object sender, SelectableConverterEventArgs e)
            {
                e.Filter = (v) =>
                    {
                        return (SampleEnum)v != SampleEnum.DISABLED_DATA1 && (SampleEnum)v != SampleEnum.DISABLED_DATA2;
                    };
                e.Source = Enum.GetValues(typeof(SampleEnum));
            }
        }
    
    
        /**********************************************************/
    
        class ComboBoxFilterTool
        {
            /// <summary>ComboBoxでキーの上下で選択可能な項目の処理をする</summary>
            private static void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
            {
                ComboBox combo = (ComboBox)sender;
                if ((Keyboard.Modifiers & ModifierKeys.None) != ModifierKeys.Alt && !combo.IsDropDownOpen)
                {
                    int index = combo.SelectedIndex;
                    int def = 0;
                    switch (e.Key)
                    {
                        case Key.Down:
                            def = 1;
                            break;
                        case Key.Up:
                            def = -1;
                            break;
                        default:
                            return;
                    }
    
                    ISelectable selectable;
                    int newindex = index;
                    do
                    {
                        newindex = newindex + def;
                        if (newindex < 0 || combo.Items.Count <= newindex)
                        {
                            newindex = index;
                            break;
                        }
                        //選択可能な項目か調べる
                        selectable = combo.Items[newindex] as ISelectable;
                    } while (selectable == null || !selectable.CanSelect);
    
                    //選択可能な項目を選択
                    combo.SelectedIndex = newindex;
                    e.Handled = true;
                }
    
            }
    
            #region フィルタ処理を有効/無効の添付ビヘイビア
    
            public static bool GetEnableFilter(DependencyObject obj) { return (bool)obj.GetValue(EnableFilterProperty); }
            public static void SetEnableFilter(DependencyObject obj, bool value) { obj.SetValue(EnableFilterProperty, value); }
            public static readonly DependencyProperty EnableFilterProperty = DependencyProperty.RegisterAttached("EnableFilter", typeof(bool), typeof(ComboBoxFilterTool), new PropertyMetadata(default(bool), new PropertyChangedCallback(OnEnableFilterPropertyChanged)));
            private static void OnEnableFilterPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                ComboBox target = dpo as ComboBox;
                if (target != null)
                {
                    bool newValue = (bool)e.NewValue;
                    bool oldValue = (bool)e.OldValue;
    
                    target.PreviewKeyDown -= ComboBox_PreviewKeyDown;
                    if (newValue)
                    {
                        target.PreviewKeyDown += ComboBox_PreviewKeyDown;
                        //target.SetBinding(ComboBox.ItemsSourceProperty, new Binding("Values"));
                        //target.DisplayMemberPath = "Value";
                        //target.SelectedValuePath = "Value";
                    }
                }
            }
    
            #endregion
        }
    
        public class SelectableConverterEventArgs
        {
            public SelectableConverterEventArgs(object customData)
            {
                this.CustomData = customData;
            }
            /// <summary>フィルタ関数</summary>
            public Func<object, bool> Filter { get; set; }
    
            /// <summary>フィルタ処理前の一覧</summary>
            public System.Collections.IEnumerable Source { get; set; }
    
            public object CustomData { get; private set; }
        }
    
        /// <summary>一覧から選択できるようにするコンバーター</summary>
        abstract class SelectableConverterBase : IValueConverter
        {
    
            public event EventHandler<SelectableConverterEventArgs> RequestFilter;
    
            protected SelectableConverterEventArgs GetFilter(object customData)
            {
                var rf = RequestFilter;
                if (rf != null)
                {
                    var e = new SelectableConverterEventArgs(customData);
                    rf(this, e);
                    return e;
                }
                return null;
            }
    
            public abstract object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    
        /// <summary>特定の型の一覧から選択できるようにするコンバーター</summary>
        /// <typeparam name="T"></typeparam>
        class SelectableConverter<T> : SelectableConverterBase
        {
            public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value is T)
                {
                    var e = GetFilter(parameter);
                    if (e != null)
                    {
                        return new ItemViewModel<T>(value, e.Source as IEnumerable<T>, e.Filter) { Value = (T)value };
                    }
                }
                return null;
            }
    
        }
    
        /// <summary>DataGridCellで表示する内容のVM</summary>
        /// <typeparam name="T"></typeparam>
        class ItemViewModel<T>
        {
            public ItemViewModel(object original, IEnumerable<T> source, Func<object, bool> filter)
            {
                OriginalSource = original;
                if (source != null)
                {
                    Values = source.Select(v => new SelectableItem<T>(v, filter(v))).ToList();
                }
            }
            /// </summary>元の値</summary>
            public object OriginalSource { get; private set; }
            /// <summary>ComboBoxの選択値</summary>
            public T Value { get; set; }
    
            /// <summary>ComboBoxに表示する一覧</summary>
            public List<SelectableItem<T>> Values { get; set; }
        }
    
        /// <summary>一覧の項目に選択可能かどうかのフラグを持たせるVM</summary>
        /// <remarks>ComboBoxの一覧で表示するかどうか</remarks>
        class SelectableItem<T> : ISelectable, System.ComponentModel.INotifyPropertyChanged
        {
            public SelectableItem(T value, bool canSelect = true) { Value = value; CanSelect = canSelect; }
            public T Value { get; private set; }
            public bool CanSelect { get; set; }
    
            public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                var pc = PropertyChanged;
                if (pc != null) { pc(this, new System.ComponentModel.PropertyChangedEventArgs(name)); }
            }
        }
    
        /// <summary>選択可能かどうかのフラグを持っているインターフェース</summary>
        interface ISelectable
        {
            bool CanSelect { get; }
        }
    }



    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年3月9日 16:54
  • gekka さん回答ありがとうございました。

    ComboBox の PreviewKeydown イベントでアプローチをするというのは、まったく思いつきませんでした。
    また、思っていたよりも、数段ややこしくてとても驚きました。動作の理解に時間がかかり、回答が遅くなりました。

    コードにおいて、私の問題を解決する上で重要なところは、
    ・ ComboBoxFilterTool クラスが添付ビヘイビアのやくわりでキーによる選択切り替えを対応
    ・ Enum の値をフィルターする仕組みが ISelectable がコアで CanSelect と、 Filter メソッドが汎用的な対応にしている

    と、思いました。
    後者が大きくなっていて、どういう仕組なのか少し難しかったですが、とても考え方の参考になりました。

    今回は、ここまで汎用性を求めなくてもよかったので、添付ビヘイビアのキーイベントの中で横着して解決することにしました。

    OnComboBox_PreviewKeyDown メソッド
    ..
    do
    {
    	newIndex = newIndex + directionValue;
    	if (newIndex < 0 || comboBox.Items.Count <= newIndex)
    	{
    		newIndex = index;
    		break;
    	}
    	selectedItem = comboBox.Items[newIndex];
    	isSelectableItem = (bool)_SelectableConverter.Convert(selectedItem, null, null, null);
    
    } while (isSelectableItem == false);
    
    public class SampleEnumSelectableConverter : IValueConverter
    {
    	public static IEnumerable<SampleEnum> _SelectableSamples;
    
    	public SampleEnumSelectableConverter()
    	{
    		_SelectableSamples = new List<SampleEnum>()
    		{
    			SampleEnum.DATA1,
    			SampleEnum.DATA2,
    			SampleEnum.DATA3,
    			SampleEnum.DATA4,
    		};
    	}
    
    	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    	{
    		var nowValue = (SampleEnum)value;
    		var enabledCount = _SelectableSamples.Where(p => p == nowValue)?.Count() ?? 0;
    
    		return enabledCount > 0 ? true : false;
    	}
    
    	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    	{
    		throw new NotImplementedException();
    	}
    }


    また、楽なので DataGrid の CellTemplate で直接編集するようにしたせいか、メニューを開いて選択したときにバインディング更新の通知がこなくなってしまいました。
    それは前回教えて頂いたバインディングの通知を強制的に送ることで一応解決できました。
    自分の思うようにコントロールをカスタマイズすると、どうしても面倒な挙動と整合性を取るのが難しいんだな、と強く感じました。どうもありがとうございました。

    private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
    	var comboBox = sender as ComboBox;
    	if (comboBox == null) return;
    
    	var binding = BindingOperations.GetBinding(comboBox, ComboBox.SelectedValueProperty);
    	if (binding == null) return;
    
    	if (binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus || binding.UpdateSourceTrigger == UpdateSourceTrigger.Default)
    	{
    		var bindingExpression = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty);
    		if (bindingExpression != null)
    		{
    			bindingExpression.UpdateSource();
    		}
    	}
    }


    作り直したコードのサンプル

    https://1drv.ms/u/s!AmGCTVXH7L6MtCmD_xl0afCEvU8X

     
    2017年3月10日 7:11
  • ichiethelさん、gekkaさん 有難うございます。
    私、このスレを読むまで、ComboBoxがキーボードで選択できることを考慮してませんでした。
    ComboBox の DropDownOpened イベントで選択肢を新たに用意することで、コードをごっそり減らせることには気づいていたのですが…

    私のやり方も晒しておきます。
    シンプルさだけは自信があります。
    <!--xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"-->
    
    <ComboBox ItemsSource="{Binding Items}"
              SelectedItem="{Binding SelectedItem}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="DropDownOpened">
                <i:InvokeCommandAction
                    Command="{Binding ItemsIsNeededCommand}"/>
            </i:EventTrigger>
            <i:EventTrigger EventName="PreviewKeyDown">
                <i:InvokeCommandAction
                    Command="{Binding ItemsIsNeededCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ComboBox>
    public enumn Item
    {
    	データ1,
    	データ2,
    	データ3,
    	データ4
    }
    VMにはItemsとSelectedItemの変更通知プロパティがあるものとします。
    // 初期化時はItemsとSelectedItemに同じ値を入れる。
    private void Load()
    {
    	Items = new [] {Item.データ1}.ToList();
    	SelectedItem = Item.データ1;
    }
    
    // ItemsIsNeededCommand の実装が呼ぶメソッド
    private void DoItemsIsNeededCommand()
    {
    	Items = Enumn.GetValues(typeof(Item))
    		.Cast<Item>()
    		.Where(i => i != Item.データ1)
    		.ToList();
    }



    2017年3月10日 9:20
  • hihijiji さん回答ありがとうございました。
    スタイルでやるかイベントで対応するかみたいなところですね。
    データのふるまいでもあるので、モデルで処理してもよいかもしれませんね。

    キーを拾ってやるやりかたについては、左右でも選択切り替えがありました。
    対応する場合は、LeftとRightも設定したほうがよいです。補足しておきます。

    switch (e.Key)
    {
    	case Key.Down:
    	case Key.Right:
    		directionValue = 1;
    		break;
    	case Key.Up:
    	case Key.Left:
    		directionValue = -1;
    		break;
    	default:
    		return;
    }



    • 編集済み ichiethel 2017年3月13日 3:05
    2017年3月13日 3:04
  • レス有難うございます。
    私が下手なコードを書いたのは失敗だったかもしれませんね。

    要約すると、
    Viewには、選択肢が必要になった事をViewModelに通知する処理だけを書いて、キーやマウスの操作自体は触りません。
    Viewから通知を受け取ったViewModelは、必要に応じてModelに問い合わせて選択肢を埋めます。
    これだけなんです。
    2017年3月13日 9:07