WPFのコンボボックスの背景色について

TreeListView上でコンボボックスを利用するときにフォーカスのあるコンボボックスの色をイベントで変えようとしたけど上手くいかなかった。

色々と調べているとXaml側のテンプレートをいじる必要があるみたいだったので、いろいろとやってみた。

とりあえず、コンボボックスをデザイナに配置し、コンボボックスを選択した状態で「右クリック」->「テンプレートの編集」ー>「コピーして編集」を選択する。


リソースの名前は後でも変更できるのと、自動で作成されるXAMLの場所も移動できるので一旦は初期のままにして「OK」を押す。


ものすごく長いリソースがWindow.Resourceの中に作成される


このリソースの中でBackgroundを管理している箇所のStyleを変更すれば変えることができる。具体的には下記の画像の部分。
※編集可能なコンボボックスを使っているときは「ComboBox.Static.Editable.Background」を変更すればよいと思われる。




今回の場合は、フォーカスの移動によって背景色を変更したかったので、「ComboBox.Static.Background」、「ComboBox.MouseOver.Background」をデザイナ上は「Transparent」に設定しておいて、プログラムからGotFocusイベントとLostFocusイベントを利用して変更している。

=== ソース抜粋1 ===
            // 選択中の背景色を設定する
            this.GotFocus += SetSelectionBackgroundColor;
            // 選択中の背景色を初期値にもどす
            this.LostFocus += ClearSelectionBackgroundColor;
==============

=== ソース抜粋2 ===

        /// <summary>
        /// フォーカス取得時の背景色
        /// </summary>
        public Brush SelectionBackgroundColor = new SolidColorBrush(Color.FromArgb(255, 250, 250, 0));

        /// <summary>
        /// 選択中のコンボボックスの背景色を設定
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SetSelectionBackgroundColor(object sender, RoutedEventArgs e)
        {
            var grid = this.Template.FindName("templateRoot", this) as Grid;
            if (grid != null)
                grid.Background = SelectionBackgroundColor;
        }

        /// <summary>
        /// 選択中のコンボボックスの背景色を初期化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearSelectionBackgroundColor(object sender, RoutedEventArgs e)
        {
            var grid = this.Template.FindName("templateRoot", this) as Grid;
            if (grid != null)
                grid.Background = null;
        }

=============
※templateRootは先ほどデザイナから自動生成されたXAMLの中で「ComboBoxTemplate」というControlTemplateがあるが、その中のGridのこと。
 なのでコンボボックスを構成する各コントロールを配置している元のGridの背景色を変更しているだけ。逆に言うとコンボボックスをどうやって作っているのかがControlTemplateを見ればわかる。


後はこの作成したリソースをコンボボックスのStyleに適用すればよい。

=== ソース抜粋3 ===
<ComboBox  ItemsSource="{Binding PriorityList, Mode=TwoWay}"
DisplayMemberPath="Name" SelectedValuePath="PriorityNo"
SelectedValue="{Binding SelectedPriorityNo}" Style="{StaticResource ComboBoxStyle1}">

=============

Window.Resourceに配置すると他のWindowでは利用できなくなるので、App.Xamlに配置すれば他のWindowから利用できる。
App.Xamlが長くなりすぎるのであれば、別のリソースディクショナリに作成して、App.Xamlは「ResourceDictionary.MergedDictionaries」でリソースの指定をすればよい。

<適用した例>


TreeListViewでのコンボボックスのデータバインドについて

TreeListViewにコンボボックスを表示するのに手間取ったのでメモ。

相変わらずゆっくりとガントチャートのアプリをいじっている。
前作っていたのはZipファイルにデータを保存する形式だったので、今度はSqliteにデータを保存する形に作り直している。

別のアプリのTaskManagerと共通のデータベースを利用すれば面白いのではないかと思って作っていたら、コンボボックスの表示がうまくできなかったので試行錯誤をしていた。



クラスとしてはこんな構造を持っていた。

メイン画面のDataContextにMainWindowViewModelをバインドして、TreeListViewには下記のような形でTreeViewへのバインドと同じような形で処理をしていた。

==== ソース抜粋 ====

               <TreeListView:TreeListView x:Name="treeView" Background="White" AllowsColumnReorder="True" ItemsSource="{Binding TreeListViewModel.TreeListItems ,Mode=TwoWay}">
                    <TreeListView:TreeListView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}" />
                    </TreeListView:TreeListView.ItemTemplate>

===============

コンボボックスの中身をバインドでやろうとして、プロパティをMainViewModelやTreeListViewModelに実装し、「ItemsSource="{Binding MainWindowViewModel.ComboboxItems,Mode=TwoWay}">」や「ItemsSource="{Binding TreeListViewModel.ComboboxItems,Mode=TwoWay}">」と指定していたが、うまく表示されない。コンボボックスを開いてみても項目の生成すらされていなかったので、バインディング自体が上手くいっていなかったので、よくよく考えるとChildにはTreeListItemをバインドしているのだから、TreeListItemクラスにコンボボックスの中身を持つ必要があるんド絵はないかと思って、TreeListItemクラスにコンボボックスの内容を作成し、バインドしてみたら上手くいった・・・。

とはいえ、TreeListItemの中身にコンボボックスの要素を実体として持つなどあほらしすぎる。
というわけで、コンボボックスの実体はTreeListViewModelに作成しておいて、参照のみTreeListItemに持つようにした。


==== ソース抜粋1 ====

   /// <summary>
    /// TreeListViewにバインディングするクラス
    /// </summary>
    public class TreeListItem : Model.ModelBase
    {
        public TreeListViewViewModel TreeListVM;
        private ObservableCollection<Model.Status> _statusList;
        /// <summary>
        /// 状況コンボボックスに表示するリストを定義
        /// データのもとはTreeListViewModelに設定をし、TreeListItemは参照(Class)のみとする。
        /// </summary>
        public ObservableCollection<Model.Status> StatusList { get { return this._statusList; } set { this._statusList = value; NotifyPropertyChanged(); } }

===============

==== ソース抜粋1 ====

    /// <summary>
        /// TreeListItemを生成したときの初期設定
        /// </summary>
        /// <param name="task"></param>
        /// <param name="treeListViewViewModel"></param>
        public void Init(Model.Task task, TreeListViewViewModel treeListViewViewModel,TreeListItem parentTree)
        {
            this.Data = task;
            this.TreeListVM = treeListViewViewModel;

            // コンボボックス用の参照をセットする
            this.StatusList = treeListViewViewModel.StatusList;
            this.DifficultyList= treeListViewViewModel.DifficultyList;
            this.PriorityList = treeListViewViewModel.PriorityList;
            this.SelectedStatsuNo = Data.CardStatusNo;
            this.SelectedDifficultyNo = Data.DifficultyNo;
            this.SelectedPriorityNo= Data.PriorityNo;


            this.Data.TreeItem = this;

            if (parentTree != null)
            {
                SetParent(parentTree, null);
            }
        }

===============


==== ソース抜粋3 ====
                        <!-- *** 状態 *** -->
                        <GridViewColumn Header="状態" Width="80"  HeaderContainerStyle="{StaticResource GridViewColumnHeaderStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
                                    <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                                        <DockPanel   >
                                            <ComboBox  VerticalAlignment="Stretch" VerticalContentAlignment="Center" ItemsSource="{Binding StatusList,Mode=TwoWay}"
                                                                                           HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                                                                                           DisplayMemberPath="Name" SelectedValuePath="StatusNo"
                                                                                           SelectedValue="{Binding SelectedStatsuNo}">
                                                <ComboBox.ItemTemplate>
                                                    <DataTemplate>
                                                        <TextBlock><Run Text="{Binding StatusNo, Mode=TwoWay}" /><Run Text=":" /><Run Text="{Binding Name, Mode=TwoWay}" /></TextBlock>
                                                    </DataTemplate>
                                                </ComboBox.ItemTemplate>
                                            </ComboBox>
                                        </DockPanel>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
===============

クラスは参照型なので、TreeListViewModel側でインスタンス化し、各選択肢をリストに追加して、TreeListItemでは参照の設定のみとしておく。

昔はClassが参照型、Structureが値型だとかは知識としては知っていたけど、あまり意識して利用するところはなかった。(それはそれでどうなのかと思うけど)
こういう時にちゃんと知っていると便利だと思う。

ComboboxのBindingについては、他のいろんなサイト見れば参考になると思う。
そもそもTreeListViewでComboboxを使うような状況になっている人がどれだけいるのか謎だけど。









Webサイトに自動でログインするVBS

サイトに自動でログインするVBSのソース

事前に対象のWebサイトを開いて、F12キーを押下して、要素の名前を調べておく。
※Edge、Chromeなどのブラウザ

objIE.Visible = Trueの部分を
objIE.Visible = Falseにしておけば、ブラウザも表示されない。

========= 以下のソース ========

OpenUrl("http://XXXXXXXX.html","uid","user1","pass","xyz)

'URLを開く
Sub OpenUrl(URL,idElement,uid,passElement,pass,loginElement)
Dim objIE
    'IE起動
    Set objIE = CreateObject("InternetExplorer.Application")
    objIE.Visible = True

    'サイトに接続
    objIE.navigate URL

    'IEを待機
    Call IEWait(objIE)

    '5秒停止
    Call WaitFor(2)

    'ユーザーIDとパスワードを設定し、ボタンをクリック
    Call IEValueSet(objIE, idElement,uid)
    Call IEValueSet(objIE, passElement,pass)
    Call IEImageClick(objIE, loginElement)

    'IEを待機
    Call IEWait(objIE)

    '5秒停止
    Call WaitFor(2)

    'IE終了
    objIE.Quit

    Set objIE = Nothing
End Sub

'画像をクリックする関数
' タグの名前が「IMG」でリンク先が引数のimageSrcと一致している場合のみクリック
Public Function IEImageClick(ByRef objIE, imageSrc)
    Dim objImage
    For Each objImage In objIE.Document.getElementsByTagName("IMG")
        If InStr(objImage.src, imageSrc) <> 0 Then
            objImage.Click
            Exit For
        End If
    Next
End Function


'値を設定する関数
Public Function IEValueSet(ByRef objIE, id, value)
    Dim obj
    Set obj = objIE.Document.getElementById(id)
    obj.value = value
End Function

'ボタンをクリックする関数
Public Function IEButtonClick(ByRef objIE, id)
    Dim objbutton
    'button要素をコレクションとして取得
    Set objbutton = objIE.document.getElementById(id)
    objbutton.click
End Function


'IEを待機する関数
Function IEWait(ByRef objIE)
    Do While objIE.Busy = True Or objIE.readyState <> 4
       WScript.Sleep 1000
    Loop
End Function

'指定した秒だけ停止する関数
Function WaitFor(ByVal second)
    Dim count

For count = 1 To second
WScript.Sleep 1000
Next

End Function

PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について  RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。 ※いざというときの手札として持っているだけで安心感が段違い  見た目上IEがインストールされていなくても、CreateObject...