C# WPF でプログラムを作成していて作ったものなど
C# WPF でプログラムを作成していて作ったものなど
サジェッション付きTextBox
ComboBox
PDFSharp の FontResolver
OpenCvSharp4 の RotatedRect の Angle で得られる角度について

サジェッション付きTextBox

PCの操作に慣れている人でも、ここに得意先コードを入れてEnterキーを押してくださいとか、
DataGridに一覧を表示しておいて、選択してくださいとか言いにくい状況がやってきたときに、
サジェッション付きTextBoxなら良いかと思ってつくったもの
ユーザーコントロールにするにはxamlが複雑すぎて、プログラムの中で使ったそのまま

こちらのサイトを参考にさせていただきました。
WPF Auto Complete/Suggestion Text Box Control

                    
        <StackPanel  HorizontalAlignment="Left" Margin="139,92,0,0" VerticalAlignment="Top">
            <TextBox x:Name="tokuimei" TextWrapping="Wrap" Text="" Width="350" TextChanged="Tokuimei_TextChanged" PreviewKeyDown="Tokuimei_OnPreviewKeyDown">
                <TextBox.InputBindings>
                    <!-- Window1は使用環境に合わせて変更してください-->
                    <KeyBinding Key="Enter" Command="{x:Static local:Window1.TokuimeiIn}" />
                </TextBox.InputBindings>
            <TextBox>
            <Popup x:Name="tokuimeiListPopup" Visibility="Collapsed" StaysOpen="False" Placement="Bottom">
                <ListBox x:Name="tokuimeiList" Visibility="Collapsed"  SelectionChanged="TokuimeiList_SelectionChanged" ItemsSource="{Binding}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <!-- TOKUINO, TOKUIMEI, Width, Margin は使用環境に合わせて変更してください-->
                                <TextBlock Text="{Binding TOKUINO}" Width="50" Margin="10" />
                                <TextBlock Text="{Binding TOKUIMEI}" Margin="10" />
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="BorderBrush" Value="LightBlue"></Setter>
                        </Style>
                    </ListBox.ItemContainerStyle>
                </ListBox>
            </Popup>
        </StackPanel>
                    
                

                    
        // このクラスの最初のほうに
                        :
        List<Tokui> tokuimeiSuggestionList = new List<Tokui>();
        public Tokui? SelectedTokui = null;
                        :
        // InitializeComponent();のあとで
                        :
            CommandBindings.Add(new CommandBinding(TokuimeiIn, TokuimeiInExec));
                        :
        // Loaded か ContentRendaed でデータベースより表示用データを読む。、Tokuis と TOKUINO の所は変更してください
                        :
        tokuimeiSuggestionList = dbContext.Tokuis.OrderBy(x => x.TOKUINO).ToList();
                        :
                        :
        // 処理コードの部分
                        :
        public static RoutedCommand TokuimeiIn = new RoutedCommand();
                        :
        private void OpenTokuimeiSuggestionBox()
        {
            try
            {
                // Enable.  
                tokuimeiListPopup.Visibility = Visibility.Visible;
                tokuimeiListPopup.IsOpen = true;
                tokuimeiList.Visibility = Visibility.Visible;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        public void CloseTokuimeiSuggestionBox()
        {
            try
            {
                // Enable.  
                tokuimeiListPopup.Visibility = Visibility.Collapsed;
                tokuimeiListPopup.IsOpen = false;
                tokuimeiList.Visibility = Visibility.Collapsed;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        private void Tokuimei_TextChanged(object sender, TextChangedEventArgs e)
        {
            try
            {
                // Verification.  
                if (string.IsNullOrEmpty(tokuimei.Text))
                {
                    // Disable.  
                    this.CloseTokuimeiSuggestionBox();
                    SelectedTokui = null;

                    // Info.  
                    return;
                }

                // Settings.  
                // TOKUIMEI の所は変更してください
                var itemSourceList = tokuimeiSuggestionList.Where(x => x.TOKUIMEI != null && x.TOKUIMEI.Contains(tokuimei.Text, StringComparison.CurrentCultureIgnoreCase)).ToList();
                if (itemSourceList.Count > 0)
                {
                    tokuimeiList.ItemsSource = itemSourceList;

                    // Enable.  
                    this.OpenTokuimeiSuggestionBox();
                }
                else
                {
                    // Disable.  
                    this.CloseTokuimeiSuggestionBox();
                    SelectedTokui = null;
                }
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        private void TokuimeiList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                // Verification.  
                if (tokuimeiList.SelectedIndex <= -1)
                {
                    // Disable.  
                    this.CloseTokuimeiSuggestionBox();

                    // Info.  
                    return;
                }

                // Disable.  
                this.CloseTokuimeiSuggestionBox();

                // Settings.
                // Tokui の所は変更してください
                SelectedTokui = ((Tokui)tokuimeiList.SelectedItem);
                // ここで選択されたデータを表示します(この例では、郵便番号や住所など。tokuimei 以外の表示項目は必要に応じて作成します)
                tokuimei.Text = SelectedTokui.TOKUIMEI;
                yubinno.Text = SelectedTokui.YUBINNO;
                jyusyo1.Text = SelectedTokui.JYUSYO1;
                jyusyo2.Text = SelectedTokui.JYUSYO2;
                telno.Text = SelectedTokui.TELNO;
                faxno.Text = SelectedTokui.FAXNO;
                tokuimeiList.SelectedIndex = -1;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        private void Tokuimei_OnPreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                this.CloseTokuimeiSuggestionBox();
            }
        }

        private void TokuimeiInExec(object sender, ExecutedRoutedEventArgs e)
        {
            // ここに tokuimei の所に入力されて Enter を押された場合の処理を書きます
                    :
        }
                    
                


上へ戻る

ComboBox

CombpoBox で IsEditable=true とした場合、
SelectionChanged イベントが最初の1文字を入力した直後に発生します
SelectedIndex が -1 かチェックすれば回避できると思いますが、 上のサジェッション付きTextBox を少し改造して作ったもの
必要な時だけPopUpListを表示したい場合(例えば郵便番号を入れて住所が表示されるとかの、キーが時々重複していてその時だけ PopUpList から選択させたい)
などにも少しの改造で使えます
(PopupList を表示させるボタンの削除と TextBox を囲っている内側の StackPanel の定義(2,3行目)を削除して、プログラムで PopupList を表示させるなど)

                
        <StackPanel  HorizontalAlignment="Left" Margin="148,53,0,0" VerticalAlignment="Top">
            <Border BorderThickness="1"  BorderBrush="#FFABADB3" Grid.Column="1" Grid.Row="1">
                <StackPanel Orientation="Horizontal">
                      <TextBox x:Name="textInputBox1" TextWrapping="Wrap" Text="" Width="180" Height="23" VerticalContentAlignment="Center" BorderThickness="0">
                        <TextBox.InputBindings>
                            <!-- Window1 は使用環境に合わせて変更してください-->
                            <KeyBinding Key="Enter" Command="{x:Static local:Window1.textInputBox1In}" />
                        </TextBox.InputBindings>
                     </TextBox>
                    <Button Width="14" Background="White" BorderThickness="0" Click="OpenPopUpListBox">
                        <Button.Content >
                            <TextBlock Text="⌄">
                            <TextBlock.RenderTransform>
                                    <ScaleTransform ScaleX="1.0" ScaleY="0.8"/>
                                </TextBlock.RenderTransform>
                            </TextBlock>
                        </Button.Content>
                    </Button>
                </StackPanel>
            </Border>
            <Popup x:Name="pouupListPopup" Visibility="Collapsed" StaysOpen="False" Placement="Bottom">
                <ListBox x:Name="popuoList" Visibility="Collapsed"  SelectionChanged="popUpList_SelectionChanged" ItemsSource="{Binding}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <!-- KUBUNMEI は使用環境に合わせて変更してください-->
                                <TextBlock Text="{Binding KUBUNMEI}" Width="180" Margin="4" />
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="BorderBrush" Value="LightBlue"></Setter>
                        </Style>
                    </ListBox.ItemContainerStyle>
                </ListBox>
            </Popup>
        </StackPanel>
                
                

                

        // InitializeComponent();のあとで
                        :
        CommandBindings.Add(new CommandBinding(textInputBox1In, textInputBox1InExec));
                        :
        // Loaded か ContentRendaed でデータベースより表示用データを読む。Kubuns と KUBUNMEI の所は変更してください
                        :
        kubuns = dbContext.Kubuns.OrderBy(x => x.KUBUNMEI).ToList();
        popUpList.ItemsSource = kubuns;
                        :
                        :
        // 処理コードの部分
                        :
        public static RoutedCommand textInputBox1In = new RoutedCommand();
                        :
        private void OpenPopUpListBox(object sender, RoutedEventArgs e)
        {
            try
            {
                // Enable.  
                popUpListPopup.Visibility = Visibility.Visible;
                popUpListPopup.IsOpen = true;
                popUpList.Visibility = Visibility.Visible;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        private void ClosepopUpListBox()
        {
            try
            {
                // Enable.  
                popUpListPopup.Visibility = Visibility.Collapsed;
                popUpListPopup.IsOpen = false;
                popUpList.Visibility = Visibility.Collapsed;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
        }

        private void popUpList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                // Verification.  
                if (popUpList.SelectedIndex <= -1)
                {
                    // Disable.  
                    this.ClosepopUpListBox();

                    // Info.  
                    return;
                }

                // Disable.  
                this.ClosepopUpListBox();

                // Settings.
                var selectedKakoKubno = ((Kubun)popUpList.SelectedItem);
                textInputBox1.Text = selectedKakoKubno.KUBUNMEI;
                popUpList.SelectedIndex = -1;
            }
            catch (Exception ex)
            {
                // Info.  
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Console.Write(ex);
            }
            // ここに popUpKist が選択された後の処理を書きます
                        :
        }

        private void textInputBox1InExec(object sender, ExecutedRoutedEventArgs e)
        {
            // ここに textInputBox1 の所に入力されて Enter を押された場合の処理を書きます
                        :
        }
                    
                


上へ戻る

PDFSharp の FontResolver

PDF の文書を作成するために、PDFsharp を使いました
nuget サイトによると、最新バージョンは 6.1.1 で .NET6.0 に対応しているとのことですが、.NET8 でも使えました(文字出力とbmp画像の張り付けだけしか試していません)
次のバージョン 6.2.0 は .NET8 に正式対応するようです
日本語を使用するのは FontResolver を用意しなければいけないのですが、いろいろ試して Arial 以外のフォントは FontResolver の記述が必要のようです
使えるフォントは ttf と otf で、windows に最初からインストールしてある 「游明朝」が日本語として使用可能です
以下のサンプルでは、游明朝 と Courier(Courier new) が使用できます
Notoフォントをインストールして使用した場合は、出力されるファイルにフォントが埋め込まれるため、1フォントあたり 10~20MB程出力ファイルが大きくなります


                    
                        :
                // プログラムの中で PDF を作成する部分
                // 新しいドキュメントを作る前に FontResolver を登録する
                PdfSharp.Fonts.GlobalFontSettings.FontResolver = new JapaneseFontResolver();
                PdfDocument document = new PdfDocument();
                        :
                        :
                // フォント指定の部分
                // フォント名は FontResolverInfo の case 文で指定したものと同じ名前を使う
                XFont font = new XFont("游明朝", 10, XFontStyleEx.Regular);
                        :
                        :



    // 日本語を使うための FontResolver
    // 
    public class JapaneseFontResolver : IFontResolver
    {
        public byte[]? GetFont(string faceName)
        {
            // Windowsのフォントフォルダーを指定する
            string? variable = System.Environment.GetEnvironmentVariable("windir");
            string fontPath = System.IO.Path.Combine(variable ?? "", "Fonts", faceName);
            if (File.Exists(fontPath) == false)
            {
                // ユーザーのフォントフォルダーを指定する
                variable = System.Environment.GetEnvironmentVariable("USERPROFILE");
                fontPath = System.IO.Path.Combine(variable ?? "", @"AppData\Local\Microsoft\Windows\Fonts", faceName);
            }
            try
            {
                using (var fontStream = File.OpenRead(fontPath))
                {
                    var fontData = new byte[fontStream.Length];
                    fontStream.Read(fontData, 0, fontData.Length);
                    return fontData;
                }
            }
            catch
            {
                Console.WriteLine("フォントファイルを開けませんでした。");
                return null;
            }
        }

        public FontResolverInfo? ResolveTypeface(string fontName, bool isBold, bool isItalic)
        {
            switch (fontName)
            {
                // caseでのフォント名はフォント指定時とここで使うだけなので、適当につけて良い
                // returnでフォントのファイル名を返す
                case "游明朝":
                    if (isBold)
                    {
                        return new FontResolverInfo("Yumindb.ttf");
                    }
                    else if (isItalic)
                    {
                        return new FontResolverInfo("Yuminl.ttf");
                    }
                    else
                    {
                        return new FontResolverInfo("Yumin.ttf");
                    }
                case "Courier":
                    if (isBold && isItalic)
                    {
                        return new FontResolverInfo("courbi.ttf");
                    }
                    if (isBold)
                    {
                        return new FontResolverInfo("courbd.ttf");
                    }
                    else if (isItalic)
                    {
                        return new FontResolverInfo("couri.ttf");
                    }
                    else
                    {
                        return new FontResolverInfo("cour.ttf");
                    }
            }
            // デフォルトのフォント
            return PlatformFontResolver.ResolveTypeface("Arial", isBold, isItalic);
        }
    }
                    
                


上へ戻る

OpenCvSharp4 の RotatedRect の Angle で得られる角度について

PCに接続したカメラを使って、伝票を取り込んでPDFにするプログラムを作成しました
RotatedRect.Angle で傾きを求めて、Cv2.WarpAffine で傾き補正を行ったのですが、得られる角度が下の図のようになっていました


RotatedRect が右に傾いていた時

RotatedRect が左に傾いていた時


左右の傾きで異なる辺の角度が得られるので Cv2.GetRotationMatrix2D に与える角度に、
得られた角度が45度以上の場合は「角度 - 90.0」として使用しました