none
DataGridでTabキーを押したときの動作について RRS feed

  • 質問

  • お世話になります。

    DataGridでTabキーを押したときの動作について質問させていただきます。

    まず以下のようにDataGridを作成しました。

    <DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
     <DataGrid.Columns>
      <DataGridTextColumn Header="名前" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
      <DataGridTextColumn Header="国語" Binding="{Binding Score国語}"></DataGridTextColumn>
      <DataGridTextColumn Header="数学" Binding="{Binding Score数学}"></DataGridTextColumn>
     </DataGrid.Columns>
    </DataGrid>

    DataGrid上でTabキーを押したところ、名前列、国語列、数学列の順にカーソルが移動するんですが、

    名前列は読み取り専用のためTabキーを押してもカーソルが行かないようにしたいです。

    つまりTabキーを押したときに、1行目の国語セル、数学セル、2行目の国語セル、数学セル・・・と移動するようにしたいんですが、

    どうすれば良いでしょうか?

    2012年12月18日 6:48

回答

  • あーなるほど・・・
    単純にDataGridCell.IsTabStopを制御するだけならこんな方法もありますよ。

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:my="clr-namespace:WpfApplication5"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <my:IsTabStopConverter x:Key="cnvTabStop"/>
            
            <Style TargetType="DataGridCell">
                <Setter Property="IsTabStop" Value="{Binding RelativeSource={RelativeSource Self},Path=Column.Header,Converter={StaticResource cnvTabStop},ConverterParameter=名前}"/>
            </Style>
        </Window.Resources>
        
        <Grid>
            <DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="名前" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
                    <DataGridTextColumn Header="国語" Binding="{Binding Score国語}"></DataGridTextColumn>
                    <DataGridTextColumn Header="数学" Binding="{Binding Score数学}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>


        class IsTabStopConverter:IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                string val = value as string;
                string param = parameter as string;
                if (val != string.Empty && param != string.Empty && val == param)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

    Column.Headerの文字列とConverterParameterが一致する場合はFalseを返すConverterを使う、という事です。
    DataGridCellそのものの別プロパティとBindingしたい場合はRelativeSource Selfを定義してやればOKです。

    ・・・ていうか、こっちの方がずっと楽だしスマートですね(苦笑)

    以上、参考までに。

    • 編集済み みっと 2012年12月19日 6:34
    • 回答としてマーク yty0918 2012年12月20日 1:29
    2012年12月19日 6:33

すべての返信

  • 面倒ですけどコードで制御しないとどうしようもないです・・・

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <Style TargetType="DataGridCell">
                <EventSetter Event="PreviewKeyDown" Handler="DataGridCell_PreviewKeyDown" />
                <EventSetter Event="GotKeyboardFocus" Handler="DataGridCell_GotKeyboardFocus" />
            </Style>
        </Window.Resources>
        
        <Grid>
            <DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="名前" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
                    <DataGridTextColumn Header="国語" Binding="{Binding Score国語}"></DataGridTextColumn>
                    <DataGridTextColumn Header="数学" Binding="{Binding Score数学}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    


            private void DataGridCell_PreviewKeyDown(object sender, KeyEventArgs e)
            {
                _key = e.Key;
            }
    
            private void DataGridCell_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
            {
                if (_key == Key.Tab)
                {
                    DataGridCell cell = (DataGridCell)sender;
                    if (cell.IsReadOnly)
                    {
                        cell.Dispatcher.BeginInvoke((Action)(()=>
                        {
                            cell.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
                        }));
                    }
                    else
                    {
                        _key = Key.None;
                    }
                }
            }

    内容はDataGridCell.GotKeyboardFocusを拾って、Tab押下後で当該セルがReadOnlyだったらフォーカスを次へ飛ばす、といういたって単純な処理です。
    ちなみにBeginInvokeしてるのはこれやっとかないと選択行の背景が変わらなくてキモチワルイからです(笑)
    汎用性を考えるならビヘイビア化しても良いかもしれません。

    ※本当はPredictFocusでFocusDirection.Nextを取れればTab押下時に処理できるのに・・・

    以上、参考までに。

    2012年12月18日 8:50
  • みっとさま

    回答ありがとうございます。

    みっとさまの回答を試してみたところ良さそうに思えたんですが、

    DataGridにフォーカスがないときにTabキーでDataGridにフォーカスが設定されると、1行目の名前列が選択されました。

    なので各列にStyleを設定してみました。

    <DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
     <DataGrid.Columns>
      <DataGridTextColumn Header="名前" Binding="{Binding Name}" IsReadOnly="True">
       <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
         <Setter Property="IsTabStop" Value="False"/>
        </Style>
       </DataGridTextColumn.CellStyle>
      </DataGridTextColumn>
      <DataGridTextColumn Header="国語" Binding="{Binding Score国語}">
       <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
         <EventSetter Event="GotFocus" Handler="DataGridCell_GotFocus"/>
        </Style>
       </DataGridTextColumn.CellStyle>
      </DataGridTextColumn>
      <DataGridTextColumn Header="数学" Binding="{Binding Score数学}">
       <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
         <EventSetter Event="GotFocus" Handler="DataGridCell_GotFocus"/>
        </Style>
       </DataGridTextColumn.CellStyle>
      </DataGridTextColumn>
     </DataGrid.Columns>
    </DataGrid>
    private void DataGridCell_GotFocus(object sender, RoutedEventArgs e)
    {
     for (int i = 0; i < dataGrid.Items.Count; i++)
     {
      var person = dataGrid.CurrentCell.Item as Person;
      if (person == null)
       continue;
      if ((dataGrid.Items[i] as Person).Name == person.Name)
      {
       dataGrid.SelectedIndex = i;
       return;
      }
     }
    }

    名前列はIsTabStopをfalseにして、Tabキーが押されても選択されないようにしました。

    国語列と数学列のGotFocusイベントでセルが選択されたときに選択行を設定して、行全体の背景が変わるようにしました。

    みっとさまの例のようにリソース化できれば良かったんですが、リソース化すると列ごとにStyleを設定するのが難しそうなので、

    個別にStyleを設定しました。

    2012年12月19日 5:26
  • あーなるほど・・・
    単純にDataGridCell.IsTabStopを制御するだけならこんな方法もありますよ。

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:my="clr-namespace:WpfApplication5"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <my:IsTabStopConverter x:Key="cnvTabStop"/>
            
            <Style TargetType="DataGridCell">
                <Setter Property="IsTabStop" Value="{Binding RelativeSource={RelativeSource Self},Path=Column.Header,Converter={StaticResource cnvTabStop},ConverterParameter=名前}"/>
            </Style>
        </Window.Resources>
        
        <Grid>
            <DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="名前" Binding="{Binding Name}" IsReadOnly="True"></DataGridTextColumn>
                    <DataGridTextColumn Header="国語" Binding="{Binding Score国語}"></DataGridTextColumn>
                    <DataGridTextColumn Header="数学" Binding="{Binding Score数学}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>


        class IsTabStopConverter:IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                string val = value as string;
                string param = parameter as string;
                if (val != string.Empty && param != string.Empty && val == param)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

    Column.Headerの文字列とConverterParameterが一致する場合はFalseを返すConverterを使う、という事です。
    DataGridCellそのものの別プロパティとBindingしたい場合はRelativeSource Selfを定義してやればOKです。

    ・・・ていうか、こっちの方がずっと楽だしスマートですね(苦笑)

    以上、参考までに。

    • 編集済み みっと 2012年12月19日 6:34
    • 回答としてマーク yty0918 2012年12月20日 1:29
    2012年12月19日 6:33
  • なるほど。

    Converterを使ってリソース化できたら、列ごとにStyleを定義する必要がなさそうですね。

    ありがとうございました。

    2012年12月20日 1:29