none
ComboBox に動的に表示又は初期値を表示させたい RRS feed

  • 質問

  • お世話になります。コンボボックスを下記のように複数項目を表示しています。
    選択専用です。


    この combo1に何件か顧客を登録してあるとします。このコンボボックスからも顧客を選択しますが、
    他にボタンで前後に移動しようとしています。

    この時、ボタンで1つ進む、又は戻るをしたときコンボボックスに表示の顧客名も同時に移動させたいのですが、
    反応しません。(そのコードは、一番下)

    質問は、下記のようにコンボボックスを表示させたときの初期値の表示と動な表示のさせ方です。
    コンボボックスのテキスト表示部分を現在の顧客名として表示させています。


    <ComboBox Name="combo1" SelectionChanged="combo1_SelectionChanged" Width="250" FontSize="14" Margin="0,0,0,-5.6" VerticalAlignment="Top" Height="28">
         <ComboBox.ItemTemplate>
              <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                          <TextBlock Text="{Binding UserID}" Margin="2.5"/>
                          <TextBlock Text="{Binding UserMei}" Margin="2.5"/>
                     </StackPanel>
               </DataTemplate>
           </ComboBox.ItemTemplate>
    </ComboBox>

    データは
    101 XXXXXXX様
    201 AAAAAAAAAAA
    205 WWWWWWWWWW
    のように表示させています。


    behind.cd
    // 顧客一覧を取得
     UserTasks user =MainWindow.user;
     this.combo1.ItemsSource = user;


    MainWindow.xaml.cs
    // 顧客名テーブル
     public static UserTasks user = new UserTasks();


    public class UserTableModel : INotifyPropertyChanged
    {
       public class USER_TABLE
       {
            public string _UserID = string.Empty;
            public string _Ryaku = string.Empty;
            public string _UserMei = string.Empty;
            public string _ZeiFlg = string.Empty;
            public string _TourokuBi = string.Empty;
            public string _CenterUserCode = string.Empty;
       }

       // 顧客修正・追加用
       public UserTasks _user;

       public IList<UserTableModel> User
       {
           get
           {
               return _user;
           }
           set
           {
               _user = (UserTasks)value;
               NotifyPropertyChanged("UserTableModel");
           }
       }

    // 次へ >>
    private void button2_Click(object sender, RoutedEventArgs e)
    {
     
     :

                MainWindow main = new MainWindow();
                UserTableModel.USER_TABLE usr = main.GetUser(mei);

                ////if (nSelectedIndex < combo1.Items.Count)
                ////    ++nSelectedIndex;
                ////combo1.SelectedIndex = nSelectedIndex;
                combo1.Text = usr._UserID + usr._UserMei;

     :
    }

    Windows 8.1/10/7 Visual studio 2015 community

    2016年1月13日 8:09

回答

  • いくつか確認させて下さい。

    1.MVVMではなくコードビハインドで書かれているのですね。コードビハインドとは、ウインドウのデザイン画面で右クリックし、「コードの表示」で表示されるところにコードを書いてプログラムを作成していくことです。

    2.ボタンという言葉が出てきますが、どのウインドウにあるのかを明確にして下さい。最初に掲載されたコードを見ると、MainWindowにあるようですが、MainWindowは子ウインドウのことではないですよね???

    3.ボタンを押したときにcombo1に表示するのは、単純に今表示されているリストデータの次を表示するのでしょうか? その場合、今表示しているcombo1に対して行うのか、それとも新たに子ウインドウを開き、そこに表示されているcombo1に対して行うのでしょうか?

    なお、合わせて前述したように、不具合がわかるような範囲のコードを掲載していただければ、的確なアドバイスが早く集まると思います。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク ferret001 2016年1月14日 2:05
    2016年1月14日 1:23
    モデレータ

すべての返信

  • 情報が少なくてよくわかりませんが、想像するにComboBoxはMainWindowにあって、button2のクリックで new MainWindow()で新たなインスタンスを作成されていますが、このMainWindowをShowするメソッドが見当たらず、表示されていないcombo1の表示が変わっているのではないでしょうか???

    いずれにしてももう少し情報がなければ、原因は想像する以外にありません。できれば現象が再現する最低限のコードを掲載していただくと、解決が早いと思われます。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2016年1月13日 8:27
    モデレータ
  • 一般論を言うなら、SelectedItemにバインディングするのがMVVMパターンでの基本ですかね。

    2016年1月13日 8:54
  • お世話になります。

    分かりにくくて申し訳ありません。combo1はMainWindowの子ウインドウに貼ってあります。

    その子ウインドウ表示時にcombo1が表示しドロップダウンリストにもデータがあります。

    ここまでは、問題ありません。

    で、そのcombo1に初期データを、子ウインドウ表示時にセットして表示させたい。

    また、ボタンを押した時の動作は、combo1のリスト内のデータを選択し順に表示させたい。

    のです。

    combo1内のリストデータが下記の場合、

    1:zzzzzzzzzzzzzz

    2;aaaaaaaaaaaaaa

    3:kkkkkkkkkkkk

    だとした場合、子ウインドウ表示時に、「1:zzzzzzzzzzzz」が表示させるとします。

    次に、ボタンをクリックすると「2:aaaaaaaaaaaa」を表示させたいのです。

    表示場所は、combo1のテキストエリアに。

    これでわかりますでしょうか?今自宅のためコードがありません。

    2016年1月13日 12:50
  • いくつか確認させて下さい。

    1.MVVMではなくコードビハインドで書かれているのですね。コードビハインドとは、ウインドウのデザイン画面で右クリックし、「コードの表示」で表示されるところにコードを書いてプログラムを作成していくことです。

    2.ボタンという言葉が出てきますが、どのウインドウにあるのかを明確にして下さい。最初に掲載されたコードを見ると、MainWindowにあるようですが、MainWindowは子ウインドウのことではないですよね???

    3.ボタンを押したときにcombo1に表示するのは、単純に今表示されているリストデータの次を表示するのでしょうか? その場合、今表示しているcombo1に対して行うのか、それとも新たに子ウインドウを開き、そこに表示されているcombo1に対して行うのでしょうか?

    なお、合わせて前述したように、不具合がわかるような範囲のコードを掲載していただければ、的確なアドバイスが早く集まると思います。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク ferret001 2016年1月14日 2:05
    2016年1月14日 1:23
    モデレータ
  • お世話になります。

    1.MVVMではなく・・・

    については、MVMでUserTableModel.csにあるデータをMainWindowで使用しています。このデータは多くのウインドウから参照しているのでMainWindowにstaticで置くことにしました。

    // 共通データー /////////////////////////////////////////////////////
    // 顧客名テーブル 

    public static UserTasks user = new UserTasks();

    2.ボタンという言葉・・

    MainWindowの子ウインドウにcombo1と同じ階層に配置しています。

    3.ボタンを押したときに・・

    その通りです。

    結果からいうと、解決しました。

    http://pro.art55.jp/?eid=1304202

    にあることをやりたかっただけなんですが、説明がうまくできなかったようです。

    ここにあるように、コンボボックスに1つの項目が並んだものを選択させるに

    comboBox.SelectedIndex = 3;

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

    で表示させられるとあったので、複数の項目を持つコンボボックスの場合も「comboBox.Text = "たちつてと";」のようなやり方で、できないか試行錯誤していました。

    結果できませんでした。

    comboBox.SelectedIndex = 3;という形で指定すればできたのですが、当初、Indexをうまく取得できなかったのでリスト内のデータで表示できないか考えていました。

    単純にコンボボックスに1つの項目のリストがある場合は、そのリスト内の項目を上記の形で与えてやればうまくいくようなんですね。

    でも、2項目以上のリストの場合、単にcomboBox.Text = "1" + "aaaaaaa";というようにやってもコンボボックスは何も表示しません。そういう仕様なのでしょうか?わかりませんが。

    comboBox.SelectedIndex = 3 結果このような形でリスト内の現在のポジションを管理しながら行うしかないようです。

    私の結論として。何かいい方法があるのでしょうか?

    2016年1月14日 2:05
  • Textではなく、SelectedItemで管理するのが基本かと思います。

    ソースコードが不完全なのでいくらか推量混じりになりますが、ComboBoxのデータソースはUSER_TABLEのコレクションということでいいんですよね。

    であれば、SelectedItemに、データソースで指定しているコレクションの中のUSER_TABLEのいずれかを設定すれば、そのUSER_TABLEに紐づけられているアイテム(ComboBoxItem)が自動的に選択されます。

    2016年1月14日 3:09
  • でも、2項目以上のリストの場合、単にcomboBox.Text = "1" + "aaaaaaa";というようにやってもコンボボックスは何も表示しません。そういう仕様なのでしょうか?わかりませんが。

    comboBox.SelectedIndex = 3 結果このような形でリスト内の現在のポジションを管理しながら行うしかないようです。

    私の結論として。何かいい方法があるのでしょうか?

    MVVM で開発されてるなら、Hongliang さんの仰るとおり、SelectedItem にバインディングするのが MVVMパターンでの定石です。

    Livet を使った簡単なサンプルを考えてみました。三番目の項目を初期値に設定し、ボタンで前後の項目に移動します。

    まず User というクラスを用意します。

    namespace ComboBoxSample.Models {
        public class User {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }

    次に ViewModel。User のコレクションと選択されている User を管理するプロパティを用意します。

    using System.Collections.ObjectModel;
    using System.Linq;
    using ComboBoxSample.Models;
    using Livet;
    using Livet.Commands;
    
    namespace ComboBoxSample.ViewModels {
        public class MainViewModel : ViewModel {
    
            /// <summary>
            /// コンストラクタ
            /// </summary>
            public MainViewModel() {
                this.Users = new ObservableCollection<User>();
                this.Users.Add(new User() { Id = 101, Name = "佐藤" });
                this.Users.Add(new User() { Id = 102, Name = "鈴木" });
                this.Users.Add(new User() { Id = 103, Name = "田中" });
                this.Users.Add(new User() { Id = 104, Name = "高橋" });
                this.SelectedUser = this.Users.ElementAt(2);
            }
    
            #region Users変更通知プロパティ
            private ObservableCollection<User> _Users;
    
            public ObservableCollection<User> Users {
                get {
                    return _Users;
                } set { 
                    if (object.Equals(_Users, value)) return;
                    _Users = value;
                    RaisePropertyChanged(nameof(Users));
                }
            }
            #endregion
    
            #region SelectedUser変更通知プロパティ
            private User _SelectedUser;
    
            public User SelectedUser {
                get {
                    return _SelectedUser;
                } set{ 
                    if (object.Equals(_SelectedUser, value)) return;
                    _SelectedUser = value;
                    RaisePropertyChanged(nameof(SelectedUser));
    
                    this.PreviewCommand.RaiseCanExecuteChanged();
                    this.NextCommand.RaiseCanExecuteChanged();
                }
            }
            #endregion
    
            #region PreviewCommand
            private Livet.Commands.ViewModelCommand _PreviewCommand;
    
            public Livet.Commands.ViewModelCommand PreviewCommand
            {
                get
                {
                    if (_PreviewCommand == null) {
                        _PreviewCommand = new Livet.Commands.ViewModelCommand(Preview, CanPreview);
                    }
                    return _PreviewCommand;
                }
            }
    
            private bool CanPreview() {
                return (this.Users.First() != this.SelectedUser);
            }
    
            public void Preview() {
                var index = this.Users.IndexOf(SelectedUser);
                if (index == 0) return;
                this.SelectedUser = this.Users[--index];
            }
            #endregion
    
            #region NextCommand
            private ViewModelCommand _NextCommand;
    
            public ViewModelCommand NextCommand
            {
                get
                {
                    if (_NextCommand == null) {
                        _NextCommand = new ViewModelCommand(Next, CanNext);
                    }
                    return _NextCommand;
                }
            }
    
            private bool CanNext() {
                return (this.Users.Last() != this.SelectedUser);
            }
    
            public void Next() {
                var index = this.Users.IndexOf(SelectedUser);
                if (index == this.Users.Count() -1) return;
                this.SelectedUser = this.Users[++index];
            }
            #endregion
        }
    }

    最後は View です。

    <Window x:Class="ComboBoxSample.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
            xmlns:vm="clr-namespace:ComboBoxSample.ViewModels"
            Title="MainWindow" Height="180" Width="400">
        <Window.DataContext>
            <vm:MainViewModel/>
        </Window.DataContext>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="24"/>
                <RowDefinition Height="24"/>
            </Grid.RowDefinitions>
            <ComboBox Width="200" Height="26" VerticalContentAlignment="Center"
                      ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}" >
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                              <TextBlock Text="{Binding Id}" Margin="2.5"/>
                              <TextBlock Text="{Binding Name}" Margin="2.5"/>
                        </StackPanel>
                    </DataTemplate>
               </ComboBox.ItemTemplate>
            </ComboBox>
            <Button Grid.Row="1" Content="前へ移動" Command="{Binding PreviewCommand}" />
            <Button Grid.Row="2" Content="次へ移動" Command="{Binding NextCommand}" />
        </Grid>
    </Window>

    何かの参考になれば幸いです。


    本フォーラムは、ユーザー(開発者)同士で情報交換を行うためのコミュニティです。初めて利用される方は、以下のアナウンスをご覧ください。 https://social.msdn.microsoft.com/Forums/ja-JP/ca9ecfb7-4407-4fcb-b8bd-207d68257e68?


    2016年1月14日 3:11
    モデレータ
  • お世話になります。

    サンプルの提示ありがとうございます。

    Livetをインストールしていなかったので確認に時間がかかりました。

    シンプルで分かりやすいですね。参考になります。

    ありがとうございます。

    2016年1月14日 6:14
  • はじめまして、art55です。
    私が書いたBlogの記事でお困りのようなので回答させていただきます。
    二つの課題があると思いますので一つずつ回答したいと思います。

    ①ComboBox.Textに値を代入した場合の挙動に関する質問。
    まずは、「ComboBoxの候補をComboBox.Textに値を指定して候補選択をしたい。」という課題です。
    サンプルコードを用意しましたので、参考にしてみてください。

    -----------------------
    MainWindow.xaml
    -----------------------

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <ComboBox Width="300" VerticalAlignment="Center" x:Name="CustomerSelector">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Id}" />
                            <TextBlock Text="{Binding Name}" />
                        </StackPanel>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Grid>
    </Window>


    -----------------------
    MainWindow.xaml.cs
    -----------------------
    using System.Collections.Generic;

    namespace WpfApplication5
    {
        public partial class MainWindow
        {
            public MainWindow()
            {
                InitializeComponent();

                CustomerSelector.ItemsSource = new List<Customer>
                {
                    new Customer {Id = 1, Name = "AAAAA"},
                    new Customer {Id = 2, Name = "BBBBB"},
                    new Customer {Id = 3, Name = "CCCCC"},
                    new Customer {Id = 4, Name = "DDDDD"},
                    new Customer {Id = 5, Name = "EEEEE"},
                };

                // "3 CCCCC"を選択する。
                CustomerSelector.Text = "3 CCCCC";
            }
        }

        public class Customer
        {
            public string Name { get; set; } = string.Empty;
            public int Id { get; set; }

            public override string ToString()
            {
                return $"{Id} {Name}";
            }
        }
    }

    解説します。ポイントはComboBox.ItemsSourceプロパティに代入しているコレクションの
    クラスのToStirngメソッドをオーバーライドさせているところです。

    仕様として記載されているわけではなく、私が検証した結果から得た結論なので確実な情報ではありませんと前置きしてきます。ComboBox.Textに値を設定するとComboBox.Itemsで設定されているコレクションの各要素の文字列と一致する最初の要素が選択状態に(たぶん)なります。
    要素が文字列の場合は、文字列で一致するかどうかで判定されます。しかし、文字列以外のオブジェクトの場合はToStringメソッドで文字列を得てから比較しているようです。「ControlTemplate」「ContentPresenter」「ItemTemplate(DataTemplate)」この辺りのキーワードを使用し、より詳細に説明することも可能かと思います。残念ながら私の力量では無理だと判断し、説明を割愛させていただきました。

    ちなみに

    https://msdn.microsoft.com/ja-jp/library/system.windows.controls.combobox.text(v=vs.110).aspx
    IsEditable プロパティが true の場合、このプロパティを設定すると、これはテキスト ボックスに表示される最初のテキストになります。 IsEditable プロパティが false の場合、この設定は無効です。

    と、書かれているので、IsEditableがfalseの場合にTextへ値を設定するのはお勧めできません。取得するのは問題ないかと思います。

    ②ComboBoxの選択行の移動に関して
    ComboBoxの選択行を別コントロールから操作して上下したいということですので、これに関して回答します。まずは、設計の観点からいうと二通りの方法があると思います。ひとつは、Viewの機能拡張を行う方法。もう一つは、ViewModelで状態管理する方法です。今回はViewを拡張したサンプルコードを紹介します。

    -----------------------
    MainWindow.xaml
    -----------------------

    <Window x:Class="WpfApplication6.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <DataTemplate x:Key="CustomerItemTemplateKey">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Id}" Margin="3,0,3,0"/>
                    <TextBlock Text="{Binding Name}" Margin="3,0,3,0"/>
                </StackPanel>
            </DataTemplate>
        </Window.Resources>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <ComboBox Grid.Row="1" Grid.Column="1"
                      x:Name="CustomerSelector"
                      ItemTemplate="{StaticResource CustomerItemTemplateKey}"
                      Width="300"/>

            <Button Grid.Row="2" Grid.Column="1"
                    Width="300"
                    Click="OnUp">前へ</Button>

            <Button Grid.Row="3" Grid.Column="1"
                    Width="300"
                    Click="OnDown">後ろへ</Button>
        </Grid>
    </Window>

    -----------------------
    MainWindow.xaml.cs
    -----------------------
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls.Primitives;

    namespace WpfApplication6
    {
        public partial class MainWindow
        {
            public MainWindow()
            {
                InitializeComponent();

                CustomerSelector.ItemsSource = new List<Customer>
                {
                    new Customer {Id = 1, Name = "AAAAA"},
                    new Customer {Id = 2, Name = "BBBBB"},
                    new Customer {Id = 3, Name = "CCCCC"},
                    new Customer {Id = 4, Name = "DDDDD"},
                    new Customer {Id = 5, Name = "EEEEE"},
                };

                CustomerSelector.SelectedIndex = 0;
            }

            private void OnUp(object sender, RoutedEventArgs e)
            {
                MoveSelectedIndex(CustomerSelector, -1);
            }

            private void OnDown(object sender, RoutedEventArgs e)
            {
                MoveSelectedIndex(CustomerSelector, 1);
            }

            private static void MoveSelectedIndex(Selector selector, int step)
            {
                if (selector.SelectedIndex < 0)
                {
                    // 未選択は操作しない。
                    return;
                }

                int newIndex = selector.SelectedIndex + step;
                selector.SelectedIndex = newIndex < 0 ? 0 : newIndex;
            }
        }

        public class Customer
        {
            public string Name { get; set; } = string.Empty;
            public int Id { get; set; }
        }
    }

    上記のコードをさらに保守性などを考慮するなら、ComobBoxを継承しRoutedUICommandでアップダウン機能を提供するカスタムコントロールを作成する方法や、ビヘイビアーを利用することでコードビハインドにコードを書かない実装もできます。この手の拡張を行えば、自分でカスタマイズしたコントロールを別のViewでも再利用できるという利点が生まれます。おそらくはViewModel側は最終的に選択されたアイテムが何かだけ知っていれば良いだけだと想像したので、今回は、この実装例を紹介いたしました。

    殴り書きになってしまいましたが、参考になれば幸いです。



    • 編集済み art55 2016年3月30日 14:56
    2016年3月30日 14:21