ラベル WPF の投稿を表示しています。 すべての投稿を表示
ラベル WPF の投稿を表示しています。 すべての投稿を表示

WPFの選択行の色変更について

【備忘録】

  WPFで選択行の色を変更したいが、フォーカスが当たっているセルについては別の色を設定したいと思ったときに色々と調べた結果、


<XAML>

                <DataGrid.Resources>
                    <Style TargetType="DataGridCell">
                        <Setter Property="BorderBrush" Value="White"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Style.Triggers>
                            <!-- セル選択時の背景色(薄い灰色) -->
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="White"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </Trigger>
                            <!-- フォーカスがあるセルだけ青枠 -->
                            <Trigger Property="IsKeyboardFocusWithin" Value="True">
                                <Setter Property="BorderBrush" Value="DodgerBlue"/>
                                <Setter Property="BorderThickness" Value="2"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                    <Style TargetType="DataGridRow">
                        <Style.Triggers>
                            <!-- セル選択時の背景色(薄い灰色) -->
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="BorderBrush" Value="Black"/>
                                <Setter Property="BorderThickness" Value="2"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.Resources>

<完成図>


<ポイント>


<!-- フォーカスがあるセルだけ青枠 -->
<Trigger Property="IsKeyboardFocusWithin" Value="True">
    <Setter Property="BorderBrush" Value="DodgerBlue"/>
    <Setter Property="BorderThickness" Value="2"/>
</Trigger>


WPFでItemTemplateに指定した内容をデザイナー画面で表示させる

1. WPFのデザイン効率を上げたい

WPFはWindowsFormと異なりデータバインドを利用することで複雑な画面をサクサク作ることができる。具体的には下記のような画面を作りたいと思ったときに特殊なコントロールを使わずに標準コントロールで1時間もあれば画面を作ることができる。


ただ、デザイナー画面ではItemTemplateに指定したユーザーコントロールが表示されないので、毎回プログラムを実行する必要があり効率が悪い。いろいろと調べた結果、下記の方法で画面のデザインを表示させることができる。

<通常>

<完成系>

2. 手順

要はデザイン中にもデータバインドをすればいいので、専用の「DemoClsMainWindowViewModel.cs」を追加する。


とりあえず、リストが表示されればいいので、カード部分の部品のClassを仮で作る
MainWindowのXamlに記載する
    <Window.Resources>
        <local:DemoClsMainWindowViewModel x:Key="DemoMainWindowViewModel"></local:DemoClsMainWindowViewModel>
    </Window.Resources>
    <Window.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource DemoMainWindowViewModel}"/>
    </Window.DataContext>

※リソースとして指定するとデザイナー内でインスタンス化されるイメージ?作成したリソースをDataContextにバインドをする

3. その他

画面にデータをバインドしていることになるので、実際にクラスのプロパティの値を指定することで、デザイナー画面でデータのバインドなどを確認することができるので、すごく効率的にデザインの確認や調整ができる。
DemoClsCardクラスにNameプロパティを作って値を設定した。
バインドされる側のユーザーコントロール「CardView.xaml」の「氏名」には「Name」プロパティがバインドされると指定されている



デザイナー画面でインスタンス時に指定した氏名が表示されている


なお、ユーザーコントロール「CardView.xaml」で「Binding」に指定しているプロパティ名と同じプロパティ名を作っておけばデザイナー編集用のクラスを作ることができる

DataGridにコンボボックスを表示する

 WPFのDataGridでコンボボックスを表示するのにちょっと詰まってしまったのでメモ。

<結論>
 バインドするデータのクラスにコンボボックスの選択肢のインスタンスを作る
 
<経緯>
 1.自動化ツール(以下、RPA)を作ろう
 2.どんな動作をするか定義をしよう
    例)
      ・マウスを(X,Y)=(20,200)へ移動
      ・クリック
      ・3秒待機
 3.動作定義のためのクラスを定義
  
        public ViewModel ThisViewModel;
        public MainWindow()
        {
            InitializeComponent();

            this.ThisViewModel = new ViewModel();

            this.ThisViewModel.Title = "タイトル";
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "マウス移動", SetValue = "20,200" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "クリック", SetValue = "" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "待機", SetValue = "2" });


            this.DataContext = this.ThisViewModel;
        }

        public class ViewModel : ModelBase
        {
            private string _title;
            public string Title
            {
                get { return this._title; }
                set { this._title = value; NotifyPropertyChanged(); }
            }

            public ObservableCollection<Action> Actions { get; set; }
            public ViewModel()
            {
                this.Actions = new ObservableCollection<Action>();
            }
        }

        #region PropertyChangeイベントを通知する処理を実装したベースクラス
        public class ModelBase : INotifyPropertyChanged
        {
            #region インターフェイス実装イベント
            public event PropertyChangedEventHandler PropertyChanged;
            public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion
        }
        #endregion

        /// <summary>
        /// 動作定義を登録するクラス
        /// </summary>
        public class Action: ModelBase
        {
            private string _actionName;
            public string ActionName
            {
                get { return this._actionName; }
                set
                {
                    this._actionName = value;
                    SetSelectedItem();
                    NotifyPropertyChanged();
                }
            }

            private string _setValue;
            public string SetValue
            {
                get { return this._setValue; }
                set
                {
                    this._setValue = value; NotifyPropertyChanged();
                }
            }

            public ComboBoxItem SelectedItem { get; set; }

            public void SetSelectedItem()
            {
                foreach (var item in ComboItems)
                {
                    if(item.Name==this.ActionName)
                    {
                        this.SelectedItem = item;
                        break;
                    }
                }
            }

            public ObservableCollection<ComboBoxItem> ComboItems { get; set; }

            public Action()
            {
                this.ComboItems = new ObservableCollection<ComboBoxItem>();
                this.ComboItems.Add(new ComboBoxItem() { Name = "マウス移動", Value = "001" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "クリック", Value = "002" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "待機", Value = "002" });
            }
        }
      
 4.下記のXAMLにバインド
    <Grid>
        <Canvas>
            <TextBox Canvas.Left="10" Canvas.Top="26" Text="{Binding Title}" TextWrapping="Wrap" Width="400"/>

            <DataGrid Height="217" Width="400" Canvas.Left="10" Canvas.Top="60" ItemsSource="{Binding Actions}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="動作名" Width="150">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding ComboItems}" Width="140"
                                                 SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                 DisplayMemberPath="Name" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="150" Header="引数" Binding="{Binding SetValue}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Canvas>
    </Grid>

 5.結果について
    DataGridのRowにバインドされているデータにコンボボックスの選択肢のインスタンスを持っておく必要がある。(検証不足?)
    ってことはDataTableをバインドして一瞬でできたぜヒャッハー!
    ってのは無理ってことなのかなぁ。まぁ入力値のチェックとかを実装することを考えるとどうしてもクラスを一つ作らざるをえないのかな・・・。
    一覧コピー的なことを一括でやろうとすると少し手間かもしれない。

 6.新規行のコンボボックスでてんやわんや
   新規行のコンボボックスにはNewItemPlaceHolderってのがバインドされているため、コンボボックスの選択肢が表示されない。
   この例でいうと「引数」列に何らかの値を入れて、行を確定させてあげると選択肢がわいてくる。何とかしたいなーって思ったので下記のサイトを見つけた。
   https://social.msdn.microsoft.com/Forums/ja-JP/7b1630de-0982-4191-bd73-3cd060e89439/wpfdatagriddatagridtemplatecolumninitializingnewitemraise?forum=wpfja
   このサイトの通りにやると一瞬ちょっとずれは生じるけどなんとかなった。
  
 7.補足
   一覧の件数がめっちゃ多い場合については表示が遅くなることがある。
   DataTemplateで作成している列については、ComboBoxがひたすら表示されているだけなので、DataGridのEditモードに移行しなくても触ることができるのが便利な反面、描画速度には問題が出ることがあるかもしれない。
   まぁ一覧を1000件表示するぐらいなら問題ないだろうし、そんなに入力できるわけないからいいと思うんだけども。
   もしどうしてもしないといけなくなったら、CellTemplateにはTextBlockでも表示しておいて、CellEditingTemplateにComboBoxを配置してあげればいい。
   DataGridTextColumnと同じでDataRowが編集モードになれば触るようになる。
    

TesseractOCRアプリケーションの作成について<手順2.WPFアプリケーションの画面作成方法(2.CsPage作成とスタイルの共通化)>

 <CsPageの作成方法について>

 プロジェクトを右クリックして、新しいファイル、ページ(WPF)を選択する。


 新しく作成したPageのxamlを修正します。

 CsWindowの時と同じで、「xmlns:controls="clr-namespace:TesseractOCR.controls"」を追加して、「<Page」を「<controls:CsPage」と修正します。

「pg01_0100_Main.xaml.cs」へ移動し、継承元のクラス名を「CsPage」へ修正するとともに「using TesseractOCR.controls;」を追加します。

 また試しに、F1キーを押下したときの処理を記述します。

 BaseWindowプロパティにインスタンスが設定されるのは、CsPageクラスがインスタンス化された後なので、PageのLoadイベント後にイベントハンドラを設定します。

※コマンドならこんな方法じゃなくていいと思いますが・・・。

<CsPageの呼び出し方>
 新しく作成したpg01_0100_Main.xamlに試しのためのTextBlockコントロールを置いて、TextBoxを配置します。

 呼び出し側)

 controls.BaseWindow window = new controls.BaseWindow(new pg01_0100_Main());

   window.Show();

 画面)


 うっすらとBaseWindowの背景色が残っています。Frameコントロールに作成したCsPageが呼び出されているのが分かります。また、BaseWindow上のボタンのContentにボタン名を設定し、Clickイベントも発生しています。

<スタイルの共通化について>
 コントロールのスタイルについてXAMLを利用するとコントロールの意味に応じたスタイルを共通で設定することができます。スタイルの継承も可能なので、下記のような形式でスタイルを一度作ってしまえば、様々なプロジェクトで利用できるのではないかと思います。

  BaseResource.xaml・・・各コントロールの基礎スタイルを定義します。

  ControlResource.xaml・・・BaseResource.xamlに定義されているスタイルを継承し、コントロールごとのスタイルを定義します。

  ProjectResource.xaml・・・ControlResource.xamlに定義されているスタイルを継承し、プロジェクトごとのスタイルを定義します。

 新しいプロジェクト(別の業務アプリを作成)するときには「BaseResource.xaml」「ControlResource.xaml」はほぼそのまま利用し、「ProjectResource.xaml」については仕様に合わせて再定義することで流用可能です。

 例)

   BaseResource.xamlに「TextBoxStyleBase」として「TextBox」のフォーカス時の背景色の設定やフォントなどを設定します。

   ControlResource.xamlに「TextBoxStyleBase」を継承して、「通常のテキストボックス(NormalTextBox)」「複数行可能なテキストボックス」のスタイルを定義します。

   ProjectResource.xamlにControlResource.xamlで定義した「通常のテキストボックス」を継承して、MaxLengthや入力文字制限を行うスタイルを定義します。


  「TextBoxStyleBase」-> 「NormalTextBox」->「txtRptIdStyle」

   ※->:は継承の意味

  こうしておくことで、業務アプリなどでよくある「得意先CD」「得意先名」といった意味のあるコントロールにすべて共通したスタイルを定義することができます。

  桁数の変更があったとしてもXAMLを修正するだけですぐに対応できます。


 <手順>

  新しいフォルダーを作成し、フォルダ名を変更します。フォルダ名:resource

  リソースディクショナリを追加します。

  追加後のフォルダ


  BaseResource.xamlについて




    今回はCsTextBoxに対してスタイルを定義しているので「xmlns:controls="clr-namespace:TesseractOCR.controls"」を記述してから、スタイルを定義しています。
 XAMLのスタイルは上に定義したスタイルを下で利用することができます。
 上図の例でいうとフォーカス時の背景色の定義である「ActiveBackColor」を一番上で定義しておくことで、「<Setter Property="Background" Value="{StaticResource ActiveBackColor}" />」で設定することができます。

   ControlResource.xamlについて
     まずは、「xmlns:controls="clr-namespace:TesseractOCR.controls"」を記述します。そのあと、先ほど定義しておいた「BaseResource.xaml」を読み込んでから継承したスタイルを定義していきます。参照方法するためには「<ResourceDictionary.MergedDictionaries>」タグを使い、読み込みたいResourceDictionaryのSourceを指定します。こうすることで、ほかのファイルに定義したスタイルを利用することができます。
     次にスタイルの継承については「BasedOn="{StaticResource LabelStyleBase}"」のように記述し、BaseResource.xamlに定義したスタイルを指定します。
     例えば先ほどの画像で定義した「TextBoxStyleBase」を継承して3つのスタイルを定義しています。「NormalTextBox」「ReadOnlyTextBox」「MultiLineTextBox」この三つのスタイルを次のProjectResource.xamlで継承し、画面コントロールごとに派生させていきます。


   ProjectResource.xamlについて
    先ほどと同じように「xmlns:controls="clr-namespace:TesseractOCR.controls"」を記述します。そのあと、先ほど定義しておいた「ControlResource.xaml」を読み込んでから継承したスタイルを定義していきます。参照方法するためには「<ResourceDictionary.MergedDictionaries>」タグを利用します。
    ここではコントロールごとのスタイルを定義します。具体的には「得意先CD」であれば最大文字数は5桁で数値入力のみ、右寄せといった具合にそのプロジェクトの中で利用する項目ごとのスタイルを定義します。基本的にはデータベースやテーブルレイアウトの定義通りになるのでスクリプトを組んで一括で作れるようにしておくと楽だと思います。
    今回の例では「帳票ID」や「JobId」がそれにあたります。数値入力のみ可能なコントロールにするには、「CsTextBox」を改修する必要がありますが、それまたの機会に記述します。
    まとめるとWPFのXAMLはCSSと同じような形で利用できるのでうまく利用しましょうということです。
      継承例)
     TextBoxStyleBase -> NormalTextBox      -> txtRptIdStyle
                                                                          -> txtJobIdStyle
              -> ReadOnlyTextBox
                                         -> MultiLineTextBox

    販売管理システムや会計管理システムそのほかの業務アプリでは同じ意味を持つ項目がいろいろな画面に置かれるので桁数のチェックや右寄せ左寄せなど本来大事にするべき業務ロジックとは関係のないところのテストや設定が必要になってしまいます。そのあたりが楽になるので、こういう機能はあるとありがたいですね。使いこなせるかというところはちょっと慣れが必要ですが、まぁ一週間毎日8時間悩んでいればいやでも身につくので頑張りましょう。










TesseractOCRアプリケーションの作成について<手順2.WPFアプリケーションの画面作成方法(1.BaseWindow作成)>


 <Windowの構成について>

 F1~F12キーをファンクションキーとして利用する構成に慣れているので、なんとか慣れ親しんだ操作法で作れるように構成を考える。

 業務用のソフトを作る期間が長かったので、ついついこの構成で作るように考えてしまう。

 

<イメージ>

 Webブラウザみたいな感じでWindowの中にFrameコントロールを設置し、各画面の設計はPageクラスを派生したCsPageで行う。

 WindowコントロールはコンストラクタでFrameに渡されたCsPageクラスをNavigateメソッドで呼び出す。

 ※Windowがブラウザ、PageがWebページのイメージ

<手順1 必要な継承コントロールの作成>

 WPF(.Net Core)のプロジェクトを作成し、プロジェクトを右クリックして「新しいフォルダー」を作成し、「controls」と名前を付ける。次にこのフォルダを右クリックして「クラス」を追加していく。


 クラスの名前はそれぞれ「CsButton」「CsCheckBox」「CsComboBox」「CsDataGrid」「CsPage」「CsTextBox」「CsWindow」で合計7つ。

 次にそれぞれのクラスファイルに対して継承元のクラスを記述していく。

           例)修正前の「CsCheckBox」

修正後の「CsCheckBox」
 ※CsWindowだけはUsingが「using System.Windows;」

 内容の記述については今後の手順で必要に応じて行っていく。

<手順2 BaseWindowの作成>

 次にPageを表示する大本となるWindowを作成する。


 作成した「controls」フォルダを右クリックして、Windowを追加し、名前を「BaseWindow」とする。Xamlを修正します。
           修正前)
           修正後)
エラーが出まくって見づらいですが、「xmlns:controls="clr-namespace:TesseractOCR.controls"」を追加し、「<Window」の部分を「<controls:CsWindow」に修正しています。
次にBaseWindow.csの内容を修正します。
修正前)

修正後)

リビルドをするとXAML側のエラーが消えます。
次にXAMLを修正していきます。

BaseWindow.xaml

<controls:CsWindow x:Class="TesseractOCR.controls.BaseWindow"
        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"
        xmlns:local="clr-namespace:TesseractOCR.controls"
        xmlns:controls="clr-namespace:TesseractOCR.controls"
        mc:Ignorable="d"
        Title="BaseWindow" Height="840" Width="990">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="5"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="5"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="5"/>
        </Grid.RowDefinitions>

        <DockPanel Grid.Row="3" Grid.Column="1" >
            <Frame HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" Name="MainFrame" Focusable="False" ></Frame>
        </DockPanel>

        <Grid Name="gdFunctionKey" Grid.Column="1" Grid.Row="4">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="12"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="10"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="5"/>
                <RowDefinition Height="13"/>
                <RowDefinition Height="2"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="5"/>
            </Grid.RowDefinitions>

            <TextBlock Grid.Column="1" Grid.Row="1" Text="F1" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton x:Name="btnF1" Content="" Grid.Column="1" Grid.Row="3"  />
            <TextBlock Grid.Column="3" Grid.Row="1" Text="F2" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF2" Content="" Grid.Column="3" Grid.Row="3"  />
            <TextBlock Grid.Column="5" Grid.Row="1" Text="F3" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton x:Name="btnF3" Content="" Grid.Column="5" Grid.Row="3"  />
            <TextBlock Grid.Column="7" Grid.Row="1" Text="F4" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF4" Content="" Grid.Column="7" Grid.Row="3"  />

            <TextBlock Grid.Column="11" Grid.Row="1" Text="F5" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF5" Content="" Grid.Column="11" Grid.Row="3"  />
            <TextBlock Grid.Column="13" Grid.Row="1" Text="F6" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF6" Content="" Grid.Column="13" Grid.Row="3"  />
            <TextBlock Grid.Column="15" Grid.Row="1" Text="F7" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF7" Content="" Grid.Column="15" Grid.Row="3"  />
            <TextBlock Grid.Column="17" Grid.Row="1" Text="F8" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF8" Content="" Grid.Column="17" Grid.Row="3"  />

            <TextBlock Grid.Column="21" Grid.Row="1" Text="F9" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF9" Content="" Grid.Column="21" Grid.Row="3"  />
            <TextBlock Grid.Column="23" Grid.Row="1" Text="F10" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF10" Content="" Grid.Column="23" Grid.Row="3"  />
            <TextBlock Grid.Column="25" Grid.Row="1" Text="F11" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF11" Content="" Grid.Column="25" Grid.Row="3"  />
            <TextBlock Grid.Column="27" Grid.Row="1" Text="F12" HorizontalAlignment="Center"></TextBlock>
            <controls:CsButton  x:Name="btnF12" Content="" Grid.Column="27" Grid.Row="3"  />
        </Grid>
    </Grid>
</controls:CsWindow>

上記のXAMLを張り付け、次はBaseWindow.csへ移動します。

/// <summary>
    /// BaseWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class BaseWindow : CsWindow
    {
        public CsPage ThisPage { get; set; }

        public BaseWindow()
        {
            InitializeComponent();
        }

        public BaseWindow(CsPage showpage)
        {
            InitializeComponent();

            this.ThisPage = showpage;
            this.ThisPage.BaseWindow = this;
            //this.ThisPage.ButtonF01 = this.btnF1;
            this.Title = this.ThisPage.Title;

            // 画面表示後にボタンのタイトルなどが設定されるのに違和感がある。コンストラクタで文字タイトルを指定すればよいが、そこまでのものとは思えないのでVisibilityで対応
            this.gdFunctionKey.Visibility = Visibility.Collapsed;

            this.MainFrame.Navigate(this.ThisPage);
            SetEvents();

        }

        public void SetEvents()
        {
            this.MainFrame.LoadCompleted += MainFrame_LoadCompleted;
            this.PreviewKeyDown += BaseWindow_PreviewKeyDown;
        }

        private void MainFrame_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
        {
            // 読込完了後に隠していたボタンを表示する
            this.gdFunctionKey.Visibility = Visibility.Visible;
        }

        /// <summary>
        /// ファンクションキー押下時のイベントを定義
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BaseWindow_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.F1:
                    this.btnF1.PerformClick();
                    break;
                case Key.F2:
                    this.btnF2.PerformClick();
                    break;
                case Key.F3:
                    this.btnF3.PerformClick();
                    break;
                case Key.F4:
                    this.btnF4.PerformClick();
                    break;
                case Key.F5:
                    this.btnF5.PerformClick();
                    break;
                case Key.F6:
                    this.btnF6.PerformClick();
                    break;
                case Key.F7:
                    this.btnF7.PerformClick();
                    break;
                case Key.F8:
                    this.btnF8.PerformClick();
                    break;
                case Key.F9:
                    this.btnF9.PerformClick();
                    break;
                case Key.F10:
                    this.btnF10.PerformClick();
                    break;
                case Key.System:
                    if (e.SystemKey == Key.F10) { this.btnF10.PerformClick(); }
                    break;
                case Key.F11:
                    this.btnF11.PerformClick();
                    break;
                case Key.F12:
                    this.btnF12.PerformClick();
                    break;
                default:
                    break;
            }

        }

    }

上記のソースを張り付けると「this.ThisPage.BaseWindow = this;」と「this.btnF1.PerformClick();」でエラーが発生します。


CsPage.csへ移動し、「public BaseWindow BaseWindow { get; set; }」を追加します。

 CsButton.csへ移動し、「using System.Windows.Automation.Peers;」「using System.Windows.Automation.Provider;」を追加し、下記のソースを追加します。


        /// <summary>

        /// Clickイベントを発生させる

        /// サンプル:https://blog.xin9le.net/entry/2013/10/27/195614

        /// </summary>

        public void PerformClick()

        {

            /// バインドしているコントロールに変更通知を送る必要があるのでフォーカスを移動

            this.Focus();

            var provider = new ButtonAutomationPeer(this) as IInvokeProvider;

            provider.Invoke();

        }


 ここまででBaseWIndowの作成は完了です。試しに呼び出してみます。
 



真っ白な画面でボタンだけが配置されています。ちょっとわかりにくいのでBaseWindow.xamlのFrameコントロール(Name=MainFrame)のBackgroundを変更します。

この黄緑色の部分がFrameコントロールになります。黄緑色の部分にCsPageクラスを表示するようにすることで、全画面で統一の見た目にそろえることができます。共通処理を入れたい場合にもBaseWindowで対応すれば何とかなることも多い、と思います。たぶん。

長くなってしまったので次回はCsPageを作成するところからスタートします。
また、スタイルの共通化もWPFの機能を使えば簡単にできるのでそちらも併せて紹介していきます。




TesseractOCRアプリケーションの作成について<手順1-1.バーコード読み取り機能を追加>

 1.ZXing.NetをNugetでインストールする


2.Bitmapを利用するためにNugetで「System.Drawing.Common」をインストールする

3.バーコード読み取りのコードを記述する
        /// <summary>
        /// OSSのZXingでバーコードの読み取りを行う
        /// </summary>
        /// <param name="path">ファイルパス</param>
        /// <returns></returns>
        private static string ReadBarcode(string path)
        {
            // 指定された画像ファイルをPictureBoxコントロール「Image1」に表示
            var source = new Bitmap(path);

            // コードの解析
            ZXing.BarcodeReader reader = new ZXing.BarcodeReader();
            reader.TryInverted = true;
            ZXing.Result result = reader.Decode(source);
            if (result != null)
            {
                Console.WriteLine("バーコードの形式:" + result.BarcodeFormat.ToString());
                Console.WriteLine("内容:" + result.Text);
                return result.Text;
            }

            return "";

        }

4.Main関数で関数を呼び出す
        static void Main(string[] args)
        {
            Console.WriteLine("バーコードの読取を開始します");
            Console.WriteLine("");
            Console.WriteLine("JANCode");
            ReadBarcode("JANCode.png");
            Console.WriteLine("");
            Console.WriteLine("Code39");
            ReadBarcode("Code39.png");
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("Nw7");
            ReadBarcode("Nw7.png");
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("QR");
            ReadBarcode("QR.png");
            Console.WriteLine("");
            Console.WriteLine("バーコードの読取を終了します");

            Console.ReadKey(true);
        }

5.プログラムを実行します。
   バーコード作成サイト:https://barcode-place.azurewebsites.net/

   読み込ませたバーコード
JANCode

Code39

QRコード
Nw7(開始と終了のアルファベットは読み取らない)


   読み取り結果

6.まとめ
 バーコードの読み取りも簡単に作成できるが、実際には元画像からバーコードを切り取る必要があるので、もう少し事前処理が必要になる。ただ、バーコード読み取り機能としてはここまでで完了となるので画像を準備するのはまた別の次回以降の記事で記載する。

TesseractOCRアプリケーションの作成について

前回の記事(https://inakaprogrammer.blogspot.com/2020/12/visualstudioocr.html)で出したOCRアプリケーションについて、作成方法のメモを残す。

 <ソリューションの構成について>

 画像の変換処理にかかる手間を考えて、下記の3つのプロジェクトに分ける
  1.画像の読取枠の定義を行うWPFアプリケーション(プロジェクト名:TesseractOCR)
     1つのパソコンで一つの起動
  2.1から呼び出されて読取枠ごとに画像を分割し、3.を呼び出すコンソールアプリケーション(プロジェクト名:OCRManager)
     1つのパソコンで一つの起動
  3.Tesseractを使ってOCRを実行するコンソールアプリケーション(プロジェクト名:OCR)
     1つのパソコンで複数の起動(上限設定可能)
 
 1つの画像に対して複数の読取枠を用意し、処理を実行するため3.を複数起動できるようにして処理速度を向上させる。
 ただし、パソコンにかかる負荷の問題もあるので同時実行の最大上限の制限をOCRManager.exeによって行う。
 ※上限を設けないと例えば100ファイル(1ファイル当たり読取枠20個)の場合、OCR.exeが同時に2000個起動することになってしまう。

<フォルダ構成>

C:.
└─TesseractOCR
    │  ER_TesseractOCR.a5er
    │  OcrDb.sqlite3
    │  TesseractOCR.iedx
    │  TesseractOCR.sln
    │  
    ├─Common                ・・・ソリューションで共通で利用する関数や定数を格納するフォルダ
    │      DataBaseAccess.cs         ・・・データベースへアクセスするためのクラスを定義
    │      SolutionCommon.cs         ・・・共通関数や定数を定義
    │      
    ├─OCR                  ・・・OCR.exeのプロジェクトのフォルダ
    │  │  Config.xml            ・・・DBへのパスや他のexeへのパスを格納
    │  │  OCR.csproj
    │  │  OCR.csproj.user
    │  │  Program.cs            ・・・画像をOCRする処理を記述
    │  ├─bin
    │  ├─obj
    │  ├─Properties
    │  │      launchSettings.json
    │  │      
    │  └─tessdata             ・・・Tesseractが利用する学習済みのデータ
    │      │  eng.traineddata
    │      │  jpn.traineddata
    │      │  jpn_vert.traineddata
    │      │  pdf.ttf
    │      │  _jpn.traineddata
    │      └─traindata
    │              Best____jpn.traineddata
    │              Best____jpn_vert.traineddata
    │              Fast____jpn.traineddata
    │              OCR3.04.00____jpn.traineddata
    │              readme.txt
    │              Ubuntu___Japanese.traineddata
    │              Ubuntu___Japanese_vert.traineddata
    │              Ubuntu___jpn.traineddata
    │              Ubuntu___jpn_vert.traineddata
    │              
    ├─OCRManager              ・・・OCRManager.exeのプロジェクトのフォルダ
    │  │  Config.xml            ・・・DBへのパスや他のexeへのパスを格納
    │  │  OCRManager.csproj
    │  │  Program.cs            ・・・画像を分割し、OCR.exeを呼び出す
    │  │  
    │  ├─bin
    │  └─obj
    │  
    └─TesseractOCR             ・・・TesseractOCR.exeのプロジェクトのフォルダ
        │  App.xaml
        │  AssemblyInfo.cs
        │  Config.xml
        │  MainWindow.xaml
        │  pg01_0100_Main.xaml
        │  pg02_0100_Rpt.xaml
        │  pg02_0200_Rpt.xaml
        │  pg02_0210_Rpt.xaml
        │  pg03_0100_Job.xaml
        │  pg03_0200_Job.xaml
        │  pg03_0210_Job.xaml
        │  pg90_0100_Opt.xaml
        │  ProjectCommon.cs
        │  TesseractOCR.csproj
        │  TesseractOCR.csproj.user
        │  
        ├─backgroundimage
        ├─bin
        ├─controls
        │      BaseWindow.xaml
        │      CsButton.cs
        │      CsCheckBox.cs
        │      CsComboBox.cs
        │      CsDataGrid.cs
        │      CsPage.cs
        │      CsTextBox.cs
        │      CsWindow.cs
        │      
        ├─modelbase
        │      ViewModelBase.cs
        │      
        ├─obj
        │                  
        └─resource
            │  BaseResorce.xaml
            │  ControlResource.xaml
            │  ProjectResource.xaml
            │  
            └─converter
                    StringToNumberConverter.cs

<主なテーブル>

 配番テーブルや設定テーブルについては、画像を参照
 ※テーブルの作成は「A5Mk2」を利用


  【TesseractOCR.exe】でデータを登録する
  ・帳票定義基本テーブル
    帳票名と備考を登録する
  ・帳票定義詳細テーブル
    読取枠の定義(XY座標、高さ、読取タイプ等)を登録する
  ・OCRジョブ基本テーブル
    OCR処理の実行指示を登録する
    ジョブの状況やダウンロード済みかどうかを判断するためのダウンロード日時を登録する
  ・OCRジョブ詳細テーブル
    OCR処理の対象となるファイルを登録する
  【OCRManager.exe】でデータを登録する
  ・OCR実行指示テーブル
    OCRジョブ詳細テーブルで指定されているファイルを帳票定義詳細テーブルで定義されている読取枠ごとに分割して、tempフォルダに配置した結果を登録する。
  【OCR.exe】でデータを更新する
  ・OCR実行指示テーブル
    OCRの結果を登録する

<プログラムの作成手順>

 人によって違いはあるとは思うが、個人的にはモチベーションの維持のために今回は下記の手順でプログラムを作成していく。
  <手順1.OCR.exeを作成> 


PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...