VBA 画面タイトルを指定しIEを待機する

VBAで基幹システム(Web)を操作するためのRPAツールを作っている。

画面に値を入力してから、内部処理が完了するまでの間に動作を待機する必要がある。一律でSleepを入れると無駄な時間が発生して動作が不安定になるのでIEの動作が完了するまで待機する処理を実装する。

うまく作れればExcelで簡単なRPAツールを作ることができる。マウス操作、クリック、指定した画面をアクティブにする、コピー、貼り付け、ぐらいは問題なく関数は作れているのであとは組み合わせるだけ。

フリーソフトをインストールする必要がないのでExcelだけ入っていれば使える簡易なRPAになる。のんびりと作っていこうと思う。


<使い方>

 ・標準モジュールに貼り付け

 ・GetIEWindowTitlesでタイトルを確認

 ・WaitIEの引数に確認したタイトルを指定(部分一致)


 Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


'IEの動作を待機する

Public Function WaitIE(ByVal strCaption) AS Boolean

    Dim shells As Object

    Dim shell As Object

    DIm tgtIE As Object

    For i = 0 To 100

        Set shells = CreateObject("Shell.Application")

        For Each shell In shells.Windows

            If TypeName(shell.document) = "HTMLDocument" Then

                If InStr(shell.document.Title,strCaption) > 0 Then

                    Set tgtIE = shell

                    Exit For

                End If

            End IF

        Next

        If Not tgtIE Is Nothing Exit For

        Sleep 100

    Next


    If tgtIE Is Nothing Then

        WaitIE = False

    Else

        'IEが動作中の場合は待つ

        Do While tgtIE.Busy = True Or tgtIE.readyState <> 4 

            DoEvents

        Loop

        Sleep 100

        WaitIE = True

    End If


End Function


'WaitIEで指定できる画面を全て取得する

Public Function GetIEWindowTitles() As String

    Dim shells As Object

    Dim shell As Object

    Dim strValue As String

    GetIEWindowTitles = ""

    Set shells = CreateObject("Shell.Application")

    For Each shell In shells.Windows

        If TypeName(shell.document) = "HTMLDocument" Then

            If strValue <> "" Then strValue = strValue & ","

            strValue = strValue & shell.document.Title

        End IF

    Next

    Set shell = Nothing

    GetIEWindowTitles = strValue

End Function


TesseractOCRアプリケーションの作成について<手順4.OCRManager.exeを作成>

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


 2.画像を切り取る

            using (Mat mat = new Mat(strTargetFilePath))
            {
                // 画像の切り抜き
                var mat2 = mat.Clone(new Rect(intX, intY, intWidth, intHeight));
                Cv2.ImWrite(strSaveFileName, mat2);
            }

 以上。

 世の中には素晴らしいOSSを作ってくれる人がいっぱいいるんだなぁ。

TesseractOCRアプリケーションの作成について<手順3.TesseractOCR.exeを作成>

<TesseractOCRアプリケーションの画面一覧について>

  ・メイン画面・・・ジョブの状況を一目で確認できるようにする
  ・帳票定義一覧・・・帳票定義の内容を一覧で表示
  ・帳票定義詳細・・・帳票定義の詳細を更新・削除
  ・読取枠定義・・・帳票定義の読取枠の定義を更新・削除
  ・ジョブ定義一覧・・・ジョブの一覧を表示
  ・ジョブ定義詳細・・・ジョブの内容を登録・削除
  ・ジョブ結果項目修正・・・OCRの結果を確認、修正

<メイン画面>

  特に複雑な画面構成ではないので、Gridコントロールを使い、DataGridを配置していく。
  

<帳票定義一覧>

  


<帳票定義詳細>

  


<読取枠定義>

  

  この画面がTesseractOCRでは一番手間がかかると思っていたが、InkCanvasを利用すると比較的簡単に処理が記述できた。
   <画像を表示しているXaml部分>
        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Grid.Row="0" >
            <InkCanvas Grid.Row="0" Name="ikcReadItemCanvas" Width="{Binding ActualWidth, ElementName=image}" Height="{Binding ActualHeight, ElementName=image}" 
                       HorizontalAlignment="Left" VerticalAlignment="Top" EditingMode="Select" >
                <InkCanvas.RenderTransform>
                    <MatrixTransform x:Name="matrixTransform" />
                </InkCanvas.RenderTransform>
                <InkCanvas.Background>
                    <VisualBrush >
                        <VisualBrush.Visual>
                            <Image x:Name="image" Stretch="Fill" Width="{Binding RelativeSource={RelativeSource Self}, Path=Source.PixelWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Source.PixelHeight}" />
                        </VisualBrush.Visual>
                    </VisualBrush>
                </InkCanvas.Background>
            </InkCanvas>
        </ScrollViewer>

   
 <呼び出し側>

        /// <summary>
        /// 画像設定処理(他の画面からこれを呼ぶ)
        /// </summary>
        /// <param name="filePath">画像ファイルパス</param>
        /// <returns>読み込み成功時true</returns>
        public bool SetImage(string filePath)
        {
            // パスが空
            if (string.IsNullOrEmpty(filePath))
            {
                return false;
            }

            var bmp = new BitmapImage();
            bmp.BeginInit();
            bmp.UriSource = new Uri(filePath);
            bmp.EndInit();
            // 画像設定
            image.Source = bmp;

            return true;
        }

  <Borderの表示方法>
     var b = new Border();
     ikcReadItemCanvas.Children.Add(b);
        InkCanvas.SetTop(b, 10);
        InkCanvas.SetLeft(b, 20);

  <保存するとき>
        y = InkCanvas.GetTop(b);
        x = InkCanvas.GetLeft(b);

  ViewModelに配列を用意しておいて、Borderをインスタンス化し、List<Border>に別管理しておいて保存時に利用するのがおすすめ。
 一応今回の画面は読取枠を表現するためにFileItemクラスというのを作成しておいて、
プロパティとしてBorderArea(Borderクラス),Title(Stringクラス)などを用意しておいた。BorderAreaプロパティは、ikcReadItemCanvasのChildrenにAddしてある。
  例)
     FileItem i = new FileItem();
         ThisViewModel.FileItems.Add(i);
      ikcReadItemCanvas.Children.Add(i.BorderArea);
         InkCanvas.SetTop(i.BorderArea, 10);
         InkCanvas.SetLeft(i.BorderArea, 20);       

<ジョブ定義一覧>

  

<ジョブ定義詳細>

  

<ジョブ結果項目修正>

  

   OCRManager.exeでOCR.exeのために画像を読取枠ごとに切り取っている。この画面はその画像を表示し、データの修正を行えるようにしている。データを修正してからダウンロードでCSVファイル化することができる。

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アプリケーションの作成について <手順1.OCR.exeを作成>

1.VisualStudioでコンソールアプリケーション(.Net Core)を作成する



2.Nugetで「Tesseract」を検索して、インストールする


3.プロジェクトを右クリックして、「新しいフォルダ」を作成し、「tessdata」と名前を付ける




4.tessdataに学習済みデータをダウンロードする
    学習済みデータ(jpn.traineddataは横書き、jpn_vert.traineddataは縦書き)
       https://github.com/tesseract-ocr/tessdata_best
  
       もしくは、https://packages.ubuntu.com/focal/tesseract-ocr-jpnから
       「[tesseract-lang_4.00~git30-7274cfa.orig.tar.xz]」をダウンロードして
       解凍した中に含まれている「jpn.traineddata、jpn_vert.traineddata」を
       コピーする。
       こちらのほうが精度が高いが解凍ソフトを別途インストールする必要あり。
       解凍ソフト:7-Zip File Manager

      ダウンロード後のフォルダ





           それぞれのファイルのプロパティを変更しないとコンパイル時に
           ファイルがコピーされない。

5.関数を追加します。
        private static void DoOcr(string ImagePath)
        {
            //using (var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default))
            using (var engine = new TesseractEngine("tessdata", "jpn", EngineMode.Default))
            {
                using (var img = Pix.LoadFromFile(ImagePath))
                {
                    using (var page = engine.Process(img))
                    {
                        // 文末の改行を除く
                        var text = page.GetText().Replace(" ", "").TrimEnd('\r', '\n');
                        Console.WriteLine(string.Format("精度: {0}", page.GetMeanConfidence()));
                        Console.WriteLine(string.Format("読取結果: \r\n{0}", text));
                    }
                }
            }
        }


6.Main関数を修正します。
        static void Main(string[] args)
        {
            Console.WriteLine("読取を開始します");

            Console.WriteLine("");
            DoOcr("OcrSample_Jpn.png");
            Console.WriteLine("");

            Console.WriteLine("読取を終了します");
            
            Console.WriteLine("複数行の読取を開始します");
            
            Console.WriteLine("");
            DoOcr("OcrSample_Jpn_Multiline.png");
            Console.WriteLine("");

            Console.WriteLine("複数行の読取を終了します");


            Console.ReadKey(true);
        }

7.結果の確認をします。
    ・読み込ませる画像(OcrSample_Jpn.png)

    ・結果
       


    ・読み込ませる画像(OcrSample_Jpn_Multiline.png)


    ・結果

8.まとめ
 フリーのOSSでここまでの精度で読めるのであれば印字文字については十分なんじゃなかろうか。学習済みデータを自分で再学習することができればさらに精度を上げられそう。できれば手書き数字と手書きアルファベットまではやってみたいな・・・。ネットで探すと元データは結構あるみたいだし。まぁ手間を考えると必要に迫られたら市販ソフトを購入したほうがいいだろうけども。





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

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