【開発】Visual Studio Online いいね!

  • 2014.03.11 Tuesday
  • 23:38
JUGEMテーマ:コンピュータ

去年のマイクロソフトカンファレンスで「Visual Studio Online」のことを初めて知りました。情弱でごめんなさい。そして、年明けくらいだったか、アカウントを取得し、先週あたりから使い始めました。うん、オラびっくりだ!TFSは以前から利用していたのですが、プライベートなコーディングに関しては、ほぼソース管理してませんでした。ブログにソースを載せているぐらいが関の山だったんです。結構面倒でした。まずブログ書かないといけませんからね(笑)それが、とりあえず思いつたときにコード書いて、チェックインってしちゃえば、もう安心。複数台PCを利用していても、簡単に共有できます。家では「開発ひとり」なのですが、PCは複数なので困ってたんです。うん!最高!Visual Studio Online 最高!ありがとう!マイクロソフト!人数制限がありますが、少数なら無料で使えるので超お勧めです!うん。最高!

【WPF】手入力不可能なComboBoxに初期値を表示するには?【修正版】

  • 2014.03.11 Tuesday
  • 23:01
JUGEMテーマ:コンピュータ

Source and Project

.NET Framework 4.5.1を利用して、DataGrid中のセルに手入力可能なComboBoxを表示させる方法を先週紹介しましたが、問題があることが分かったので、今回は修正版のソースコードを紹介したいと思います。

以前紹介した投稿は以下となります。
【WPF】DataGridに編集可能なComboBoxを表示するには?

問題点の指摘は以下の投稿となります。
【WPF】「手入力不可能なComboBoxに初期値を表示するには?」の問題点

問題点は、挙動のおかしさを指摘したにとどまっていますが、実装・設計面から見てもStyleの定義でテーブルのカラム名を指定しているなど、ちょっと汎用性に欠ける点も問題点といえば問題点ではないかと思います。今回はそれを含めて解決してみました。

ソースコードを紹介します。

<Window x:Class="Art55.DataGridComboBoxDemo20140311_01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:demo="clr-namespace:Art55.DataGridComboBoxDemo20140311_01"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{StaticResource MainWindowViewModelKey}">
    <Window.Resources>
        <demo:FromTextToItemsSourceConvter x:Key="FromTextToItemsSourceConvterKey" />
        <demo:FromItemsSourceToSelectedIndexConverter x:Key="FromItemsSourceToSelectedIndexConverterKey" />

        <Style TargetType="ComboBox" x:Key="EditingElementStyleKey">
            <Setter Property="IsEditable" Value="True" />
        </Style>

        <Style TargetType="ComboBox" x:Key="ElementStyleKey">
            <Setter Property="ItemsSource"
                    Value="{Binding Path=Text,
                                    RelativeSource={RelativeSource Self},
                                    Converter={StaticResource FromTextToItemsSourceConvterKey}}" />
            <Setter Property="SelectedIndex"
                    Value="{Binding Path=ItemsSource,
                                    RelativeSource={RelativeSource Self},
                                    Converter={StaticResource FromItemsSourceToSelectedIndexConverterKey},
                                    Mode=OneWay}"></Setter>
        </Style>

    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid CanUserAddRows="False"
                  CanUserDeleteRows="False"
                  AutoGenerateColumns="False"
                  DataContext="{Binding Path=Source}"
                  ItemsSource="{Binding .}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="No" Binding="{Binding No}"></DataGridTextColumn>
                <DataGridComboBoxColumn Header="ComboBox Column"
                                        TextBinding="{Binding Value, UpdateSourceTrigger=PropertyChanged}"
                                        EditingElementStyle="{StaticResource EditingElementStyleKey}"
                                        ElementStyle="{StaticResource ElementStyleKey}">

                </DataGridComboBoxColumn>
            </DataGrid.Columns>
        </DataGrid>

        <Button Grid.Row="1" Command="{Binding RejectChangedAllRowsCommand}">変更取消</Button>
    </Grid>
</Window>

using System;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using Art55.DataGridComboBoxDemo20140311_01.Annotations;

namespace Art55.DataGridComboBoxDemo20140311_01
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            _source = new DataTable {CaseSensitive = true};
            _source.Columns.Add("No", typeof(int));
            _source.Columns.Add("Value");
            _source.Rows.Add(new object[] { 1, DBNull.Value });
            _source.Rows.Add(new object[] { 2, "A2" });
            _source.Rows.Add(new object[] { 3, "A3" });
            _source.Rows.Add(new object[] { 4, DBNull.Value });
            _source.Rows.Add(new object[] { 5, DBNull.Value });
            _source.Rows.Add(new object[] { 6, "A6" });
            _source.Rows.Add(new object[] { 7, "A7" });
            _source.Rows.Add(new object[] { 8, DBNull.Value });

            _source.AcceptChanges();

            RejectChangedAllRowsCommand = new CommandObject(this,
                _ => _source.RejectChanges());
        }

        public ICommand RejectChangedAllRowsCommand { get; private set; }

        // TODO コマンドを用意してDataTableを操作してみると面白いよ。

        private readonly DataTable _source;

        public DataTable Source
        {
            get { return _source; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class FromTextToItemsSourceConvter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null || value == DependencyProperty.UnsetValue)
            {
                return DependencyProperty.UnsetValue;
            }
            return new[] {value};
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

    public class FromItemsSourceToSelectedIndexConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var items = value as object[];
            return items == null ? -1 : 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }


    public class CommandObject : ICommand
    {
        public CommandObject(INotifyPropertyChanged viewModel, Action<object> execute)
            : this(viewModel, execute, null)
        {
        }

        public CommandObject(INotifyPropertyChanged viewModel, Action<object> execute, Func<object, bool> canExecute)
        {
            if (viewModel == null)
                throw new ArgumentNullException("viewModel");

            if (execute == null)
                throw new ArgumentNullException("execute");

            viewModel.PropertyChanged += OnViewModelPropertyChanged;
            _execute = execute;
            _canExecute = canExecute;
        }

        void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CheckCanExecute();
        }

        public void CheckCanExecute()
        {
            InvokeCanExecuteChanged(new EventArgs());
        }

        public void Execute(object parameter)
        {
            _execute.Invoke(parameter);
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute != null)
                return _canExecute.Invoke(parameter);
            return true;
        }

        public event EventHandler CanExecuteChanged;

        private void InvokeCanExecuteChanged(EventArgs e)
        {
            EventHandler changed = CanExecuteChanged;
            if (changed != null)
                changed(this, e);
        }

        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
    }
}

ポイントは、赤字で書いた部分です。

        <Style TargetType="ComboBox" x:Key="EditingElementStyleKey">
            <Setter Property="IsEditable" Value="True" />
        </Style>

        <Style TargetType="ComboBox" x:Key="ElementStyleKey">
            <Setter Property="ItemsSource"
                    Value="{Binding Path=Text,
                                    RelativeSource={RelativeSource Self},
                                    Converter={StaticResource FromTextToItemsSourceConvterKey}}" />
            <Setter Property="SelectedIndex"
                    Value="{Binding Path=ItemsSource,
                                    RelativeSource={RelativeSource Self},
                                    Converter={StaticResource FromItemsSourceToSelectedIndexConverterKey},
                                    Mode=OneWay}"></Setter>
        </Style>


1.編集中は、ComboBox.IsEditableにTrueを指定する。
2.参照中は、ComboBox.Textの変更を監視し、変更のあったタイミングでItemsSourceに入力文字を設定する。
3.参照中は、ComboBox.ItemsSourceを監視し、ComboBox.ItemsSourceの数に応じてSelectedIndexを設定する。

監視対処が

Text ← ItemsSource ← SelectedIndex

と、XAMLで定義しました。以前書いたコードは、DataRowの特定のカラム値やDataRowViewの変更を監視していたりと、タイミングに不整合が起きていたのですが、今回は、それを解消してみました。

 手入力可能なComboBoxを表示させる方法はいくつかあります。前に書いた方法としてDataGridTemplateColumnを利用するというのも一つの手です。WpfToolkit時代はDataGridComboBoxColumnは編集時はComboBox、参照時はTextBlockを利用していたと記憶していますが、DataGridTemplateColumnを利用すれば、近いものが実装できると思います。
 それと、今回の実装では、まだ機能的にはお粗末です。すぐにわかるのが、一つは入力開始制御がF2とダブルクリック以外でできていない点です。ほかにもあるかもしれません。という事で、ないと思いますが、このコードを利用する場合は注意してください。

Source and Project

【WPF】「手入力不可能なComboBoxに初期値を表示するには?」の問題点

  • 2014.03.08 Saturday
  • 07:41
JUGEMテーマ:コンピュータ

【WPF】手入力不可能なComboBoxに初期値を表示するには?
http://pro.art55.jp/?eid=1304202


上記の記事でDataGrid中に編集可能なComboBoxを表示させる方法を紹介しましたが、一部問題があることが分かったので、今回は、問題点を考察したいと思います。解決案は別途投稿する予定です。

では、問題点をあげます。

・操作によって参照モードで値があるにも関わらず、値が表示されないことがある。

ということが、わかりました。操作方法は以下の通り。

1.適当にセルを編集する。
2.編集したセルが見えないようにDataGridをスクロールする。
3.編集したセルの値をもとに戻す(DataRow.RejectChangedメソッドを呼び出す)
4.もとに戻したセルを表示させ、もう一度編集する。
5.編集したセルを表示させた状態で、セルの値をもとに戻す。

そうすると、編集したセルに値が空になります。この問題がどうして発生しているかというのが今回の本題となります。RejectChangedを呼び出す処理が入った状態のコードを以下に紹介します。

 

<Window x:Class="Art55.EditableComboBoxDataGrid20140308_02.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:editableComboBoxDataGrid2014030802="clr-namespace:Art55.EditableComboBoxDataGrid20140308_02"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>

        <editableComboBoxDataGrid2014030802:TextBlockComboBoxItemsSourceConverter x:Key="TextBlockComboBoxItemsSourceConverterKey" />

        <!-- 編集モード時のComboBox -->
        <Style TargetType="ComboBox" x:Key="EditingElementStyle">
            <Setter Property="IsEditable" Value="True" />
        </Style>

        <!-- 参照モード時のComboBox -->
        <Style TargetType="ComboBox" x:Key="TextBlockComboBoxStyle">
            <!--<Setter Property="Text" Value="{Binding Column1}" />-->
            <Setter Property="ItemsSource"
                     Value="{Binding .,
                             Converter={StaticResource TextBlockComboBoxItemsSourceConverterKey},
                             ConverterParameter=Column1}" />

        </Style>

        <!-- 既定のDataGridのスタイル -->
        <Style TargetType="DataGrid">
            <Setter Property="AutoGenerateColumns" Value="False" />
            <Setter Property="CanUserAddRows" Value="False" />
            <Setter Property="CanUserDeleteRows" Value="False" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="SampleDataGrid">
            <DataGrid.Columns>
                <DataGridComboBoxColumn Header="Column1"
                                        TextBinding="{Binding Column1, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                         EditingElementStyle="{StaticResource EditingElementStyle}"
                                         ElementStyle="{StaticResource TextBlockComboBoxStyle}"
                                         />
            </DataGrid.Columns>
        </DataGrid>

        <Button Grid.Row="1" Click="OnSelectedRowsRejectChanges">選択行の値を元に戻す。</Button>
    </Grid>
</Window>

using System;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace Art55.EditableComboBoxDataGrid20140308_02
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var dataTable = new DataTable { TableName = "SampleData" };
            dataTable.Columns.Add("Column1");
            dataTable.Columns.Add("Column2");
            dataTable.Columns.Add("Column3");

            Enumerable.Range(0, 300)
                 .Select(n => new object[] { "A" + n, "B" + n, "C" + n })
                 .ToList()
                 .ForEach(item => dataTable.Rows.Add(item));

            dataTable.AcceptChanges();

            SampleDataGrid.ItemsSource = dataTable.DefaultView;
        }

        private void OnSelectedRowsRejectChanges(object sender, RoutedEventArgs e)
        {
            SampleDataGrid
                .SelectedItems
                .OfType<DataRowView>()
                .Select(rowView => rowView.Row)
                .Where(row => row != null)
                .Where(row => row.RowState == DataRowState.Modified)
                .ToList()
                .ForEach(row => row.RejectChanges());
        }
    }

    public class TextBlockComboBoxItemsSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string columnName = (parameter as string) ?? string.Empty;
            var dataRowView = value as DataRowView;
            if (dataRowView == null
                || dataRowView.Row == null
                || !dataRowView.Row.Table.Columns.Contains(columnName))
            {
                return DependencyProperty.UnsetValue;
            }
            return new[] { dataRowView.Row[columnName] };
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

上記のコードのどこが問題かというとDataGridComboBoxColumn.ElementStyle用に用意したStyleです。

        <!-- 参照モード時のComboBox -->
        <Style TargetType="ComboBox" x:Key="TextBlockComboBoxStyle">
            <Setter Property="ItemsSource"
                     Value="{Binding .,
                             Converter={StaticResource TextBlockComboBoxItemsSourceConverterKey},
                             ConverterParameter=Column1}" />

        </Style>

ここのSetter部分ですが、DataRowViewを監視し、DataRowViewに変更があった際に、コンバートした値をComboBox.ItemsSourceプロパティに設定するというBindingを定義しています。問題なのはDataRowViewを監視しているという点です。DataRowView["Column1"]の値の変更を監視しているわけではないという点です。DataGridComboBox.TextBindingにより、編集中のComboBoxも編集中でないComboBoxもTextプロパティはDataRowView["Column1"]を監視しているのですが、DataRowView自体を監視しているわけではありません。よって、ComboBox.TextとComboBox.ItemsSourceの同期が取れていないという事になり、たとえばComboBox.Textには何らかの値があるにも関わらずComboBox.ItemsSourceに候補がないとか、ComboBox.ItemsSourceに値はあるけどComboBox.Textと不一致になるという問題が発生しうる事になります。

なので、今回見つけた問題が発生することになります。修正しなければならないのですが、今回の投稿は問題点を指摘するだけにとどめておきます。また、次回。

【WPF】DataGrid中の任意のセルを編集中にする。

  • 2014.03.06 Thursday
  • 01:24
今回は「DataGrid中の任意のセルを編集中にする。」方法の一案を紹介します。別にこの方法で必ず実現する必要なんてないんだからね!と、まあ、ツンデレ風な表現を用いたかっただけです。ごめんなさい。

DataGrid中の任意のセルを編集中にするには、前回紹介した

【WPF】DataGrid中の任意のセルにフォーカスを当てる。
http://pro.art55.jp/?eid=1304203


の手続きを踏んだ後、ひと手間加えるだけで、やりたいことが実現できます。手順は以下の通り。

1.フォーカスを当てたいDataGridInfoを生成または取得する。
2.DataGridInfoからセルのコンテンツを表示しているコントロールを取得する。
3.コンテンツを表示しているコントロールからDataGridCellコントロールyを取得する。
4.DataGridCellコントロールにフォーカスを当てる。
5.DataGridを編集開始にする。

5番目の手続きが増えただけです。5番目の手続きは、私が知る限りでは3つ方法があります。

A.
DataGrid.BeginEditメソッドを呼び出す。
B.DataGrid.BeginEditCommand.Executeメソッドを呼び出す。
C.DataGridCell.IsEditingプロパティにtrueを設定する。
Aはイベントドリブンな実装やマークアップ拡張の実装で利用できると思います。Bはコマンド用ですが、コードからでも呼び出せます。Cは編集中にするだけのシンプル状態変更です。編集中に切り替わるにあたり、編集開始の状態である場合に付随するフォーカス制御などはしてくれません。逆に言えば、編集中の初期状態を自前で制御したい場合は、Cを選択するべきです。AとBは実行すると、例えばDataGricTextBoxColumnを指定した場合は、編集中になった場合に、TextBox内のTextが全選択になりますし、先日紹介した手入力可能なDataGridComboBoxColumnを指定した場合もTextBox内のテキストが全選択上になります。便利です。

ということでサンプルコード。


<Window x:Class="Art55.DataGridCell20140305_001.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">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="dataGrid" />
        <Button Grid.Row="1" Click="OnClickick">40行2列目のセルを編集モードにする。</Button>
    </Grid>
</Window>
 

using System.Data;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace Art55.DataGridCell20140305_001
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            DataTable source = CreateSampleData();
            dataGrid.ItemsSource = source.DefaultView;
        }

        private void OnClickick(object sender, RoutedEventArgs e)
        {
            var dataGridCellInfo = new DataGridCellInfo(dataGrid.Items[40], dataGrid.Columns[2]);
            dataGrid.CurrentCell = dataGridCellInfo;
            dataGrid.ScrollIntoView(dataGridCellInfo.Item);
            DoEvents();
            FrameworkElement contentElement = dataGridCellInfo.Column.GetCellContent(dataGridCellInfo.Item);
            if (contentElement == null)
            {
                return;
            }
            var dataGridCell = contentElement.Parent as DataGridCell;
            if (dataGridCell == null)
            {
                return;
            }
            dataGridCell.Focus();

            // IsEditingを利用した場合、コンテンツを表示しているコントロールにフォーカスが当たらないなどの諸問題がある。
            // dataGridCell.IsEditing = true;

            // DataGrid.BeginEditCommand.Execute(null, dataGridCell);

            dataGrid.BeginEdit();
        }

        private static void DoEvents()
        {
            // NOTE: http://msdn.microsoft.com/ja-jp/library/system.windows.threading.dispatcher.pushframe(VS.80).aspx
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrames), frame);
            Dispatcher.PushFrame(frame);
        }

        private static object ExitFrames(object f)
        {
            ((DispatcherFrame)f).Continue = false;
            return null;
        }

        private static DataTable CreateSampleData()
        {
            var source = new DataTable();
            source.Columns.Add("Column1");
            source.Columns.Add("Column2");
            source.Columns.Add("Column3");
            source.Columns.Add("Column4");

            Enumerable.Range(0, 100)
                .GroupBy(n => Enumerable
                    .Range(0, source.Columns.Count)
                    .Select(m => ((char)('A' + m))
                        .ToString(CultureInfo.InvariantCulture) + n)
                    .Cast<object>()
                    .ToArray()
                    , (n, m) => n)
                .ToList()
                .ForEach(item => source.Rows.Add(item));

            source.AcceptChanges();
            return source;
        }
    }
}


まとめると

DataGridの任意のセルを編集モードにしたい場合は

1.セルにフォーカスを当てる。
2.
DataGrid.BeginEditメソッドを呼び出す。

です。フォーカスを当てるのが結構難があります。詳しくは以下を参照してください。
【WPF】DataGrid中の任意のセルにフォーカスを当てる。
http://pro.art55.jp/?eid=1304203

 

【WPF】DataGrid中の任意のセルにフォーカスを当てる。

  • 2014.03.06 Thursday
  • 00:43
今回は「DataGrid中の任意のセルにフォーカスを当てる。」方法を紹介します。
簡単な手順を紹介すると

1.フォーカスを当てたいDataGridInfoを生成または取得する。
2.DataGridInfoからセルのコンテンツを表示しているコントロールを取得する。
3.コンテンツを表示しているコントロールからDataGridCellコントロールyを取得する。
4.DataGridCellコントロールにフォーカスを当てる。
という手順となるわけですが、一言でいうとDataGridCellコントロールにフォーカスを当てるってことになります。手順を書き下しましたが、問題があります。「コンテンツを表示しているコントロール」や「DataGridCellコントロール」というのは書いてある通り「コントロール」なので設定によっては表示されていない状態だと、コントロールのインスタンスが存在しない事になります。うん、忘れてました。なので、今回はその回避方法も含めて紹介します。WPFToolkitを利用している際に、覚えてテクニックです(.NET Framework 4.5.1でもやらないといけないのだろうか・・・もっと賢くエレガントな方法ってあるのかな。)

さっきの手順を追記します。

1.フォーカスを当てたいDataGridInfoを生成または取得する。
+.DataGridInfoで指定したセル(DataGridCellコントロールおよびそのコンテンツ)のインスタンスを強引に生成する。
2.DataGridInfoからセルのコンテンツを表示しているコントロールを取得する。
3.コンテンツを表示しているコントロールからDataGridCellコントロールyを取得する。
4.DataGridCellコントロールにフォーカスを当てる。
 
これでできます。以下、サンプルコードですが、サンプルコードでは、強引にスクロールさせて、さらにプライオリティがBackgroundで指定されているイベントを強引に発砲させてレタリングさせ、取得したいDataGridCellとそのコンテンツ用コントロールを取得しました。
<Window x:Class="Art55.DataGridCell20140305_001.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">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="dataGrid" />
        <Button Grid.Row="1" Click="OnClickick">40行2列目のセルにフォーカスを当てる。</Button>
    </Grid>
</Window>
using System.Data;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace Art55.DataGridCell20140305_001
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            DataTable source = CreateSampleData();
            dataGrid.ItemsSource = source.DefaultView;
        }

        private void OnClickick(object sender, RoutedEventArgs e)
        {
            var dataGridCellInfo = new DataGridCellInfo(dataGrid.Items[40], dataGrid.Columns[2]);
            dataGrid.ScrollIntoView(dataGridCellInfo.Item);
            DoEvents();
            FrameworkElement contentElement = dataGridCellInfo.Column.GetCellContent(dataGridCellInfo.Item);
            if (contentElement == null)
            {
                return;
            }
            var dataGridCell = contentElement.Parent as DataGridCell;
            if (dataGridCell == null)
            {
                return;
            }
            dataGridCell.Focus();
        }

        private static void DoEvents()
        {
            // NOTE: http://msdn.microsoft.com/ja-jp/library/system.windows.threading.dispatcher.pushframe(VS.80).aspx
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrames), frame);
            Dispatcher.PushFrame(frame);
        }

        private static object ExitFrames(object f)
        {
            ((DispatcherFrame)f).Continue = false;
            return null;
        }

        private static DataTable CreateSampleData()
        {
            var source = new DataTable();
            source.Columns.Add("Column1");
            source.Columns.Add("Column2");
            source.Columns.Add("Column3");
            source.Columns.Add("Column4");

            Enumerable.Range(0, 100)
                .GroupBy(n => Enumerable
                    .Range(0, source.Columns.Count)
                    .Select(m => ((char)('A' + m))
                        .ToString(CultureInfo.InvariantCulture) + n)
                    .Cast<object>()
                    .ToArray()
                    , (n, m) => n)
                .ToList()
                .ForEach(item => source.Rows.Add(item));

            source.AcceptChanges();
            return source;
        }
    }
}

ポイントをまとめると

1.DataGridCell.Focus()を呼び出すことで、セルにフォーカスを当てることができる。
2.DataGridCellは仮想モードの場合、見えていないと存在しない可能性がある。つまり見せる必要がある。

以上

【WPF】手入力不可能なComboBoxに初期値を表示するには?

  • 2014.03.01 Saturday
  • 15:25
JUGEMテーマ:コンピュータ

 WPFのComboBoxには、二つのモードが存在します。一つは、手入力不可のモード。もう一つは、手入力可能なモードです。手入力不可能なモードは、基本的に、ComboBoxをドロップダウンした時に出てくる候補を選択することを目的としたコンロトールですので、候補以外の値を初期値として表示することはできません。逆に、手入力可能な場合は、候補以外の値も入力可能で、初期値もなんでも入ります。という仕様になっているため前回紹介した

【WPF】DataGridに編集可能なComboBoxを表示するには?
http://pro.art55.jp/?eid=1304201


で紹介させていただいたような、手入力可と不可が切り替わるような状況で技術的な問題が発生するということになります。前回は、これをDataGridの問題としてとらえ、解決案を提案させていただきましたが、今回はComboBoxの動きをもう少し正確に見ていきたいと思います。ちなみに個人的にはComboBoxは嫌いなコントロールです。タッチ操作もしづらいこともあり、そのうちなくなってくれたらうれしいな〜なんて思っていたりします。消えろ!

手入力不可能なComboBoxに初期値を表示したい場合として、コードで記述すると以下のコードとなります。

            ComboBox comboBox = ...
            comboBox.ItemsSource = new[]
            {
                new { Text = "あいうえお", Value = 0 },
                new { Text = "かきくけこ", Value = 1 },
                new { Text = "さしすせそ", Value = 2 },
                new { Text = "たちつてと", Value = 3 },
            };

            comboBox.SelectedIndex = 3;

        <ComboBox ...
                  DisplayMemberPath="Text">

上のコードでは「たちつてと」というレコードを選択するために、SelectedIndexプロパティに3を設定しました。
これを実行すれば初期値が


ほかにも方法があり、ちょっとトリッキーな感じがしますが、以下でもいけます。

            ComboBox comboBox = ...
            comboBox.ItemsSource = new[]
            {
                new { Text = "あいうえお", Value = 0 },
                new { Text = "かきくけこ", Value = 1 },
                new { Text = "さしすせそ", Value = 2 },
                new { Text = "たちつてと", Value = 3 },
            };

            comboBox.Text = "たちつてと";

        <ComboBox ...
                  DisplayMemberPath="Text">

わざわざItemsSourceに匿名クラスのインスタンスを突っ込んでいるので、これで初期表示が「たちつてと」となるのが、不思議な感じがしますが、実際に実行してみると3行目のオブジェクトが選択状態になります。この辺りは、ComboBoxに存在する依存関係プロパティが相互にうまいこと作用してくれるのだと思うしかありません。

で、よくやらかす間違いがこれです。

            ComboBox comboBox = ...

            comboBox.Text = "たちつてと";
            comboBox.ItemsSource = new[]
            {
                new { Text = "あいうえお", Value = 0 },
                new { Text = "かきくけこ", Value = 1 },
                new { Text = "さしすせそ", Value = 2 },
                new { Text = "たちつてと", Value = 3 },
            };

        <ComboBox ...
                  DisplayMemberPath="Text">



C#コードで書くと、基本上から順に実行されるため、先にTextプロパティに値を設定させてしまいます。そうすると、候補に存在する値のみ表示可能という制限に引っ掛かり値が設定されません。デバッグで追ってみるとわかりますが、Textプロパティには、設定した値が、設定されています。ただし、SelectedItem等の選択状態を表すプロパティは未選択な状態を返してきます。

とまあ、強引にTextプロパティからでも選択状態を変更できるということを紹介したかったのですが、デメリットもあるということです。

まとめると

1.手入力不可能なComboBoxの初期値を表示するには、候補を選択上にする必要がある。
2.選択状態の変更の手段としてSelectedXXXXプロパティ以外にもTextプロパティも利用できる。
3.Textプロパティを利用するメリットとして、可視化された実際に値を直接指定できる。
4.Textプロパティを利用するデメリットとして、ほかのプロパティも手順は意識する必要はあるが、
  選択状態を変更する意図としては、読めず表示する値を変更しているようにしか見えないため、  
  Textプロパティに関しては誤解が生じやすいコードとなる。

まあ、ComboBoxは嫌いですわ。本当。

【WPF】DataGridに編集可能なComboBoxを表示するには?

  • 2014.03.01 Saturday
  • 14:06
JUGEMテーマ:コンピュータ
-----------------------------------------------------------------------------------
本投稿で紹介した内容に問題があったため以下の投稿で修正版を紹介しています。
【WPF】手入力不可能なComboBoxに初期値を表示するには?【修正版】
-----------------------------------------------------------------------------------

 久々にWPFネタを書いてみようと思います。今回は「DataGridに編集可能なComboBoxを表示するには?」というタイトルです。ターゲットは.NET Framework 4.5.1です。大昔にWPFTookit(最新版ではないと思います)で同じことを実現するコードは紹介したような気がしまうが、正式に.NET FrameworkがDataGridをサポートするようになってからは、技術情報を載せた記憶がないので、これが初めてだと思います。そんなことはどうでもいいのですが、重要な要点は、「過去の記事は古いから参考にならないよ」ってところですかね。まるで他人事みたいな言い方でごめんなさい。

 前置きは、これぐらいにしておいて、今回実現することは、以下の要件を満たすことです。

1.DataGridで値を表示する。
2.セルは編集可能であること。
3.編集中のセルはComboBox(ドロップダウンリストが表示されるコントロール)であること。
4.編集中のセルは手入力可能であること。

この要件を満たすにあたり技術的な問題が発生します。いや、無知だと発生するというだけです。何かしらの解決策をご存知の方は発生しません。いや、本当。無知な私は以下の問題が発生しました。

いろいろ試行錯誤したんですが、「いろいろ」なのでとりあえず、シンプルに書いたコードだけ紹介します。

<Window x:Class="ComboBoxDataGrid20140301_001.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:y="clr-namespace:ComboBoxDataGrid20140301_001"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <!-- 既定のDataGridのスタイル -->
        <Style TargetType="DataGrid">
            <Setter Property="AutoGenerateColumns" Value="False" />
            <Setter Property="CanUserAddRows" Value="False" />
            <Setter Property="CanUserDeleteRows" Value="False" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="SampleDataGrid">
            <DataGrid.Columns>
                <DataGridComboBoxColumn Header="Column1"
                                        TextBinding="{Binding Column1, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                        />
            </DataGrid.Columns>
        </DataGrid>
       
        <Button Grid.Row="1" Click="OnContentChanged">メモリ上のデータを表示する。</Button>
    </Grid>
</Window>

using System.Data;
using System.Linq;
using System.Text;
using System.Windows;

namespace ComboBoxDataGrid20140301_001
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            var dataTable = new DataTable() { TableName = "SampleData" };
            dataTable.Columns.Add("Column1");
            dataTable.Columns.Add("Column2");
            dataTable.Columns.Add("Column3");

            Enumerable.Range(0, 10)
                .Select(n => new object[] { "A" + n, "B" + n, "C" + n })
                .ToList()
                .ForEach(item => dataTable.Rows.Add(item));

            SampleDataGrid.ItemsSource = dataTable.DefaultView;
        }

        private void OnContentChanged(object sender, RoutedEventArgs e)
        {
            string message = SampleDataGrid
                .ItemsSource
                .OfType<DataRowView>()
                .Select(rowView => string.Join(", ", rowView.Row.ItemArray))
                .Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line))
                .ToString();
            MessageBox.Show(message);
        }
    }
}

上記のコードを実行してみると、要件を全然満たせていないことがわかります。

1.参照モードで値が表示されていない。
2.編集開始時に値が表示さていない。
3.編集中のComboBoxコントロールは手入力できない。

唯一編集中はセルがComboBoxになるという点だけ要件を満たせています。うん。どうしたものかといところです。この問題を解決するには、大きく分けて二つ手があると思います。DataGridComoBoxColumnのEditingElementStyleおよびElementStyleからStyleを変更し、要件を満たす。もう一つはDataGridComboBoxColumnの利用をやめて、DataGridTemplateColumnを利用する。今回はStyleを編集する方向で、実現することにしました。

変更したソースは以下です。

<Window x:Class="ComboBoxDataGrid20140301_001.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:y="clr-namespace:ComboBoxDataGrid20140301_001"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>

        <y:TextBlockComboBoxItemsSourceConverter x:Key="TextBlockComboBoxItemsSourceConverterKey" />

        <!-- 編集モード時のComboBox -->
        <Style TargetType="ComboBox" x:Key="EditingElementStyle">
            <Setter Property="IsEditable" Value="True" />
        </Style>

        <!-- 参照モード時のComboBox -->
        <Style TargetType="ComboBox" x:Key="TextBlockComboBoxStyle">
            <!--<Setter Property="Text" Value="{Binding Column1}" />-->
            <Setter Property="ItemsSource"
                    Value="{Binding .,
                            Converter={StaticResource TextBlockComboBoxItemsSourceConverterKey},
                            ConverterParameter=Column1}" />
        </Style>

        <!-- 既定のDataGridのスタイル -->
        <Style TargetType="DataGrid">
            <Setter Property="AutoGenerateColumns" Value="False" />
            <Setter Property="CanUserAddRows" Value="False" />
            <Setter Property="CanUserDeleteRows" Value="False" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="SampleDataGrid">
            <DataGrid.Columns>
                <DataGridComboBoxColumn Header="Column1"
                                        TextBinding="{Binding Column1, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                        EditingElementStyle="{StaticResource EditingElementStyle}"
                                        ElementStyle="{StaticResource TextBlockComboBoxStyle}"

                                        />
            </DataGrid.Columns>
        </DataGrid>

        <Button Grid.Row="1" Click="OnContentChanged">メモリ上のデータを表示する。</Button>
    </Grid>
</Window>

using System;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace ComboBoxDataGrid20140301_001
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            var dataTable = new DataTable { TableName = "SampleData" };
            dataTable.Columns.Add("Column1");
            dataTable.Columns.Add("Column2");
            dataTable.Columns.Add("Column3");

            Enumerable.Range(0, 10)
                .Select(n => new object[] { "A" + n, "B" + n, "C" + n })
                .ToList()
                .ForEach(item => dataTable.Rows.Add(item));

            SampleDataGrid.ItemsSource = dataTable.DefaultView;
        }

        private void OnContentChanged(object sender, RoutedEventArgs e)
        {
            string message = SampleDataGrid
                .ItemsSource
                .OfType<DataRowView>()
                .Select(rowView => string.Join(", ", rowView.Row.ItemArray))
                .Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line))
                .ToString();
            MessageBox.Show(message);
        }
    }

    public class TextBlockComboBoxItemsSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string columnName = (parameter as string) ?? string.Empty;
            var dataRowView = value as DataRowView;
            if (dataRowView == null
                || dataRowView.Row == null
                || !dataRowView.Row.Table.Columns.Contains(columnName))
            {
                return DependencyProperty.UnsetValue;
            }
            return new[] { dataRowView.Row[columnName] };
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

}

すべてのを説明するのは面倒なので、要点だけ書くと

1.ComboBoxを手入力可能な設定にした。IsEditable=True
2.参照モードのComboBoxに値を表示できる値は、候補の値に限られているため
  参照モードのみ候補に値を一件仕込んだ。
  <Setter Property="ItemsSource" ... />

 上記のことをすれば、参照モードでは、値が表示され、編集中は手入力可能なComboBoxが表示されるようになります。補足としては、データソースにDataTableを利用しています。それぞれのカラムにプリミティブな値しか設定しませんが、データソース側に多少の努力を加えると、IValueConverterあたりの下りは不要になるかもしれません。この辺は開発・保守の観点から費用対効果で結論を出すべきところですかね。今回は、知ってる知識で最短で組める方法でがんばってみました。


 今回はComboBoxに候補を出すということはしていませんが、これに関しても色々方法があり、満たしたい要件で実装方法、難易度(知ってれば難易度なんてあがりませんが)が上がります。

 以上で、今回は以上で終わります。久々にBlogを書くこと書き方忘れますね。

-----------------------------
(追記 2014/03/05)
Styleに不要なSetterがあったため修正しました。
誤)
<Setter Property="Text" Value="{Binding Column1}" />
正)
<!--<Setter Property="Text" Value="{Binding Column1}" />-->

TextBindingで上書きされる項目なので指定しても無効となります。

最近、何もかもが使いにくい。

  • 2014.03.01 Saturday
  • 12:23

お家で、outlookを使っていたら、上記のエラーで出てきました。
うーん。

最近、イラっとするアプリが多いような気がする。

【散財】surface Pro 2 買った。

  • 2013.12.15 Sunday
  • 20:54

Surface Pro 2を買っちゃいました。
これでようやくWindows8向けのアプリ開発の技術調査と検証ができます。

貧乏が憎い。

【書籍】メタプログラミング .NET

  • 2013.08.30 Friday
  • 20:35
評価:
Kevin Hazzard,Jason Bock
アスキー・メディアワークス
¥ 3,360
(2013-08-29)

JUGEMテーマ:コンピュータ

楽しみにしていた一冊が手元に届きました。とはいっても、予約していたのに、すっかり忘れていたんだけど(笑)

--------------------------
まだ第一章しか読んでいないので帯に書かれている目次を羅列しておきます。

第1部 メタプログラミングの秘密のベールをはがす
 第1章 メタプログラミングの概念
 第2章 リフレクションでコードとメタデータを探る
第2部 コード生成のテクニック
 第3章 T4(Text Template Transformation Toolkit)
 第4章 CodeDOMを使ったコード生成
 第5章 Reflection.Emitによるコード生成
 第6章 式によるコード生成
 第7章 ILの書き換えによるコード生成
第3部 言語とツール
 第8章 DLR
 第9章 言語とツール
 第10章 .NETコンパイラを管理する
付録A Windows 8でのメタプログラミング
付録B 用途のガイドライン

という内容になっています。第4章から第7章の内容を楽しみにしています。前々から知りたかった内容だったのですが、ググってもなかなかお目当ての内容が見つからず・・・それらしきテクニックが使われているコードは何度も見かけては興奮したものですが、解説はない・・・・(笑)

calendar

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     
<< September 2019 >>

あわせて読みたい

あわせて読みたいブログパーツ

selected entries

categories

archives

recent comment

  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    art55 (05/22)
  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    分解大好き (05/18)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/04)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    Gen (02/04)
  • 【キーボード】RealForce が壊れて帰ってきた。
    art55 (04/29)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/23)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    かるあ (02/22)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    art55 (01/16)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    karuakun (01/16)
  • 【NetOffice】【Excel】死なないExcelプロセスをKillする。
    art55 (12/05)

recent trackback

recommend

recommend

recommend

C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION)
C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION) (JUGEMレビュー »)
Sasha Goldshtein,Dima Zurbalev,Ido Flatow,サシャ・ゴルドシュタイン,ディマ・ズルバレフ,イド・フラトー

recommend

ろんりと集合
ろんりと集合 (JUGEMレビュー »)
中内 伸光
とてもわかりやすいです。

recommend

recommend

シャノン・ノイマン・ディジタル世界
シャノン・ノイマン・ディジタル世界 (JUGEMレビュー »)
市川 忠男
4章がリレーショナルデータベースな内容になってます。ページ数があまりありませんが、ポイントがものすごく的確にまとまっていて、感動します。

recommend

recommend

東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS
東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS (JUGEMレビュー »)

テンキーレス、静音のRealForce91UBK-S。スコスコ感がたまらなく気持ちいいです。家と会社で2台持ってます。

recommend

recommend

プログラミング.NET Framework 第4版 (プログラミングシリーズ)
プログラミング.NET Framework 第4版 (プログラミングシリーズ) (JUGEMレビュー »)
Jeffrey Richter
発売予定美 2013年10月10日。.NET Frameworkとお付き合いする人のバイブルですね。

recommend

recommend

キャット・シッターの君に。
キャット・シッターの君に。 (JUGEMレビュー »)
喜多嶋 隆
私のイラストレータデビュー本です。

recommend

Essential .NET ― 共通言語ランタイムの本質
Essential .NET ― 共通言語ランタイムの本質 (JUGEMレビュー »)
ドン・ボックス,クリス・セルズ,Don Box,Chris Sells,吉松 史彰

links

profile

search this site.

others

mobile

qrcode

powered

無料ブログ作成サービス JUGEM