Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9743

C# ドッキングウィンドウとRichTextBoxの継承 (RETROF-16統合開発環境 序章)

$
0
0

はじめに

本記事の内容

本記事は以下の外観と機能を持つアプリケーションの作り方を解説します。一部の自動生成されるソースコードを除く全ソースコードを公開します。
なお、このアプリ自体はキーボードから何を入力しても「Unknown command」を返すだけであり、具体的な事は何もできません。しかしこのアプリのソースコードに順次手を加えて行く事により、様々なアプリケーションに変身する事が可能です。すなわち、ここで紹介するアプリは同様の画面構成をを持つ全てのアプリの土台として利用することを想定しています。

WS2019_000697.jpg

プログラミング上の主なテーマは以下の通り。

  • 複数のドッキングウィンドウを持つアプリの作成方法
  • 既存のコントロールの継承方法と、その具体的な利用方法
  • 継承コントロールからの独自イベントの発生方法と、その受取方法

尚、ここで紹介するアプリは筆者が別途作成した「RETROF-16統合開発環境」の土台となっています。このため本記事のタイトルに(RETROF-16統合開発環境 序章)という文言が含まれていますが、本記事内では「RETROF-16統合開発環境」に関する説明は一切ありません。本記事はあくまでも本記事内で完結する「Unknown command」を返すだけのアプリの作り方を紹介するものです。

開発環境等

  • OS Windows10 バージョン 1909 ビルド 18363.1016
  • Microsoft Visual Studio Community 2019 Version 16.7.2
  • Microsoft .NET FrameWork Version 4.8.03752

下準備

本アプリの開発用のフォルダ作成

C:直下にR16BLACKの名でフォルダを作ります。C:直下に作るのは好きではありませんが、こちらの方が何かと説明が楽なのでこうしました。実際にはどこでも構いません。
R16BLACKは、ここではその意味の詳細説明は割愛します。「筆者にとって特別な意味あるワード」とお考え下さい。これも実際には任意の名で構いません。

必要なDLL

R16BLACKフォルダに「WeifenLuo.WinFormsUI.Docking.dll」を置きます。
このDLLの入手方法やその役割は「WeifenLuo.WinFormsUI.Docking.dll download」等で検索するとダウンロード可能なサイトがいくつか見つかると思います。
https://www.dllme.com/dll/
上記はその一例ですが、サイトによっては悪意のある改変dllを提供するサイトも存在します。ダウンロードに関する危険性は全て自己責任で行って下さい。

プロジェクトの作成

次に、普通にプロジェクトを作成します。プロジェクト名は任意ですが、ここでは「RETROF」としたものとして話を薦めます。

ここでビルド(及び実行)を行うと、いつもの以下の画面が出ます。通常のツール開発では、ここにフォーム等を配置して行きますが、ここではその前に一工夫します。

ドックパネルの採用

様々な機能を有するウィンドウズアプリを開発する場合、ウィンドウ(フォーム)をいくつかの区画に分割し、その区画単位に各々の機能の入出力領域を割り振るのが一般的な作り方です。
勿論、アプリによってはこの作り方で十分な場合をありますが、ここでは更に一歩進んだ「ドックパネル」を全面的に採用します。

ドックパネルとは

ドックパネルとはパネルの一種で、そのパネル(以下、親パネル)に含まれる子パネル(以下、ドッキングウィンドウ)の配置をユーザー自身が自由に決めることができる機能です。子パネルを親パネルの外に出すと子パネルは独立したフォームの様に振舞います。

VS(Visual Studio)自身がこの機能を使ったアプリケーションですので、VSをお使いの方は必ずこの機能を使っているはずです。しかし不思議な事に、このドックパネルを用いたアプリ開発はVS自身が持つ標準機能では実現できません。このため、前述の「WeifenLuo.WinFormsUI.Docking.dll」の取り込みが必要になります。

(補足)他のDLLに関して

ドックパネルを実現するDLLは複数あります。本ツールでは「WeifenLuo.WinFormsUI.Docking.dll」を利用しましたが他のDLLとの性能比較はしてません。従ってこれが最適なDLLなのかは分かりません。単に最もポピュラーなDLLに思えたので、これを採用しました。

ドックパネルを使える様にする手順

既に「Docking.dll」が取り込まれているVSの場合

「ツールボックス」を開き、右クリックで「アイテムの選択」を選択。
下記のダイアログボックスが現れるので、「WeifenLuo.WinFormsUI.Docking.dll」を選択」。
(初めてドックパネルを使う場合は、「WeifenLuo.WinFormsUI.Docking.dll」は現れませんので、キャンセルをして次項の “初めて「Docking.dll」を使うVSの場合” に進んで下さい。

WS2019_000674.jpg
「OK」ボタンを押しダイアログを閉じると、ツールボックスに「DockPanel」が出現します。

初めて「Docking.dll」を使うVSの場合

「ツールボックス」を開き、右クリックで「アイテムの選択」を選択。
上記のダイアログボックスが現れます。但し「WeifenLuo.WinFormsUI.Docking.dll」はそこにはありません。
参照ボタンを押し、「C:\R16BLACK\Docking.dll」を指定すると、ダイアログに「Dock Panel」が現れますので、それを指定して「OK」。

(参考)ドックパネルが使えるツールボックスと使えないツールボックス

右がドックパネルが使えるようになったツールボックスです。ドックパネルが使えるようになった後でアルファベット順にソートしていない場合は、追加された「Dock Panel」はリストの末尾に現れますので注意して下さい。

  

親パネルを含むフォームの生成

ツールボックスから必要なアイテムを選びフォームに貼り付けます。以下の表は貼り付けるアイテムの一覧です。メニューバーやツールバーも中身は空のままで構わないのでここで貼り付けておきます。
各コントロールの名前は任意で構いませんが、VSが暗黙で決める名前(種別+数字)は区別しずらいのでお勧めできません。

名前種別生成位置補足説明
MainFormForm(全体)最初から存在するので貼り付け不要
MenuBarMenuStripFormの最上部Windowsアプリの標準的な要素
ToolBarToolStripMenuStripの下Windowsアプリの標準的な要素
StatusBarStatusStripFormの最下部Windowsアプリの標準的な要素
MainDockDockPanelFormの残り領域全体配置には外部DLLが必要

ここまでの実行結果

この状態でビルド(及び実行)を行うと、以下に示す形状のアプリが起動します。
見かけ上の形状は、C#によるウィンドウズアプリ構築の入門書等ででよく見かけますが、中央の灰色の広い領域が「ドッキングパネル」という特殊な領域になっている点が一般の入門書と異なります。
WS2019_000673.jpg

ドッキングウィンドウ(子パネル)の配置

今回用意するドッキングウィンドウ(子パネル)は下記の表の通りです。現時点では全く必要がなく、その存在理由も曖昧なものも含まれますが、ここでまとめて作ります。
生成位置と占有サイズは仮の値です。後で使い勝手を見ながら微調整します。
ここでは詳細説明は割愛しますが、ドッキングウィンドウを追加したり削除する事も後で簡単に行う事ができます。

名前生成位置と
占有サイズ
背景色生成目的(ここでは目的は
まだ意味を持ちません)
MapWinMaindockの
右側40%
薄桃メモリマップを表示予定
SrcWinMaindockの
上部30%
薄黄ソースプログラムのエディタを
構築予定
CslWin上記2つの
残り領域
灰色コンソール風の会話領域を
構築予定
ObJWinSrcWinの
右側30%
薄緑RETROF-16のメモリダンプを
表示予定
EmuWinObJWinの
上部40%
濃紺RETROF-16のエミュレータを
構築予定

子パネルの生成方法

EmuWinを例として手順を紹介します。

この手順内では、まだ各々の子パネルの生成位置と占有サイズはまだ未定となります。また子パネルそのものもビルド(及び実行)をしてもまだ現れません。

手順1

「プロジェクト」⇒「新しい項目の追加」、現れたダイアログの左ペインで「Windows Forms」を選択、右ペインで「継承フォーム(Windows フォーム)」を選択し、名前をEmuWinとして「追加」。
WS2019_000693.jpg

手順2

継承ピッカーが現れますので、そのまま何もせずに「参照」をクリックし、現れたエクスプローラで、本記事冒頭でダウンロードした「WeifenLuo.WinFormsUI.dll」を指定。

手順3

継承ピッカーの項目が増えます。「DockContent」を選択し「OK」。
WS2019_000695.jpg

手順4

作成した子パネルに対する、普通のフォームと同様のデザイナーが表示されます。
ここでは背景色のみ変更し、他は何もせずデザインを終了します。

WS2019_000696.jpg

手順5

他の子パネルも同様に生成します。

ソースコードの変更

この時点でビルドしても何も変化はありません。生成した子パネルを表示させるには、直接ソースファイルに手を加える必要があります。手を加えるソースファイルはMainForm.csです。生成(new)と、表示位置、サイズを指定しているだけですので、特に解説は不要かと思います。

MainForm.csの全リスト(クリックで展開)
MainForm.cs
usingSystem.Windows.Forms;usingWeifenLuo.WinFormsUI.Docking;namespaceRETROF{publicpartialclassMainForm:Form{publicMainForm(){InitializeComponent();//-------------------------------------------//フォームをMDIコンテナにするIsMdiContainer=true;//ドッキングウィンドウ以外の領域(ドキュメント領域)をSDIドキュメントとする。MainDock.DocumentStyle=DocumentStyle.DockingSdi;//子パネルの生成CslWinCslWin=newCslWin();EmuWinEmuWin=newEmuWin();ObjWinObjWin=newObjWin();SrcWinSrcWin=newSrcWin();MapWinMapWin=newMapWin();//子パネルの表示MainDock.DockRightPortion=0.4;MapWin.Show(MainDock,DockState.DockRight);MainDock.DockTopPortion=0.3;SrcWin.Show(MainDock,DockState.DockTop);CslWin.Show(MainDock,DockState.Document);ObjWin.Show(SrcWin.Pane,DockAlignment.Right,0.3);EmuWin.Show(ObjWin.Pane,DockAlignment.Top,0.4);//-------------------------------------------}}}

 

MainForm.csを変更後、ビルド(及び実行)を行うと、以下の外観を持つアプリが現れます。
(フォームのタイトルは「ドッキングウィンドウの実例」に変更しています)

WS2019_000672.jpg

RichTextBoxの継承

既存コントロールを単に継承するだけならば、その方法の詳細は「C# 既存コントロール 継承」等で検索すると多くの記事が見つかると思います。
ここでは標準コントロールであるRichTextBoxを単純に継承する方法ではなく、具体的な意味を持つ独自コントロールを作る方法を解説します。
具体的には標準コントロールであるRichTextBoxを継承し、独自コントロールShellTextBoxを作る方法となります。

まずはRichTextBoxを単純継承しShellTextBoxを作る

これは機械的な作業ですので手順の概略のみ以下に示します。

1. [プロジェクト(P)]⇒[新しい項目の追加(W…)]
2. 現れたダイアログで『ユーザーコントロール(Windowsフォーム)』を選択
3. 『名前』を「ShellTextBox.cs」として『追加』
 
4. 上記の絵が出るので、この絵の上で右ボタンメニュー[コードの表示]
5. 継承元がUserControlになっているので、これをRichTextBoxに変更
6. ビルドするとShellTextBox.Designer.の27行目でエラーが出るのでこの行をコメントアウト(MS公認バグ?)

7. 再度ビルドし、ツールボックスにShellTextBoxが現れることを確認

ShellTextBoxを単純継承した状態で使ってみる

ツールボックスに現れたShellTextBoxをデザイナーを用いてCslWinの全面(DockプロパティをFillに設定)に名前をShellとして貼り付け、ビルド(及び実行)します。

Shellと言う名前について

プログラム的には、この名前自体は任意で構いませんが、ここでは以降の解説の都合上Shellとして下さい。デザイナーは名前をshellTextBoxを暗黙で設定しますから、これをShellに変更が必要です。

この時点での実行結果

WS2019_000694.jpg

継承したとは言え、機能はRichTextBoxのままですから当然、RichTextBoxを張り付けた場合と何ら変わりません。

ShellTextBox独自の処理を実装する

ShellTextBox.csに独自コードを書き込みます。継承元のRichTextBoxに加わる主な機能は以下の通りです。

  • 任意の文字列からなるプロンプトの表示
  • プロンプト領域を侵すカーソル移動(BSキーや左矢印キー)を制限
  • プロンプトに対する「文字列+Enterキー」入力に対する独自イベントの発生
  • プロンプト、入力文字列、出力文字列の色分け機能の実装

具体的なコードは以下となります。コードの説明はコード中にコメントとして記載してますので、それを参照して下さい。

ShellTextBox.csの全リスト(クリックで展開)
ShellTextBox.cs
usingSystem.Windows.Forms;usingSystem.Drawing;namespaceRETROF{///補助クラス、EventArgsクラスを継承するためだけに存在publicclassShellEventArgs:System.EventArgs{publicstringCommand;publicstringResult;}///独自クラスpublicpartialclassShellTextBox:RichTextBox{///プロパティprivatestringPrompt="RETROF>";privateconststringLFCR="\r\n";privateconstcharCR='\n';privateconstcharCOUTIONCHER='!';privateColorResultColor;privateColorPromptColor;privateColorCommandColor;privateColorCoutionColor;///デリゲートの宣言 (独自に継承したShellEventArgs型の引数を伴う)publicdelegatevoidShellEventHandler(objectsender,ShellEventArgse);///上記デリゲートのインスタンスを(イベント型として)定義publiceventShellEventHandlerKeyEnter;///本クラスのコンストラクタpublicShellTextBox(){//RichTextBoxのアジア圏のフォント化け(公認バグ?)の抑制LanguageOption=RichTextBoxLanguageOptions.UIFonts;Prompt="";//仮設定、正式な設定はInitialize()で行う}///初期化publicvoidInitialize(stringprompt,Colorprompt_color=default(Color),Colorcommand_color=default(Color),Colorcoution_color=default(Color)){Prompt=prompt;PromptColor=(prompt_color!=default(Color))?prompt_color:SelectionColor;CommandColor=(command_color!=default(Color))?command_color:SelectionColor;CoutionColor=(coution_color!=default(Color))?coution_color:Color.Red;ResultColor=SelectionColor;ShowPrompt();}///MyEnterが空(==null)ではない事を確認し、「Enterキーが押されたイベント」を発行するprotectedvirtualvoidOnConsole(ShellEventArgse){//KeyEnter(this, e); //確認しないならこちらで十分KeyEnter?.Invoke(this,e);}///(オーバーライド)マウスクリックによるカーソル移動を強制的に末尾にするprotectedoverridevoidOnMouseUp(MouseEventArgse){base.OnMouseUp(e);SelectionStart=int.MaxValue;//領域末尾を超す値の指定は領域末尾に移動する}///(オーバーライド)矢印キー、BSキー、Enterキーの挙動変更protectedoverridevoidOnKeyDown(KeyEventArgse){//カーソル上下移動の無効化if(e.KeyCode==Keys.Up||e.KeyCode==Keys.Down){e.Handled=true;return;}//プロンプト文字列領域を侵すBackSpace(もしくはカーソル左移動)の無効化if(e.KeyCode==Keys.Left||e.KeyCode==Keys.Back){if(GetColumn()<Prompt.Length+1){e.Handled=true;return;}}if(e.KeyCode!=Keys.Enter)return;//Enterキー以外なら終わり//以下はEnterキーが押下された場合ShellEventArgsee=newShellEventArgs();ee.Command=GetCommand();   //コマンド(入力文字列)をShellEventArgsに設定OnConsole(ee);   //実行   SelectionColor=ResultColor;//実行結果の表示色を設定//表示文字列の先頭が「!」なら、その一文字を削除し、表示色をCoutionColorに変更if(ee.Result!=null&&ee.Result.Length!=0&&ee.Result[0]==COUTIONCHER){ee.Result=ee.Result.Remove(0,1);SelectionColor=CoutionColor;}//実行結果の表示AppendText(LFCR+ee.Result+LFCR);ShowPrompt();e.Handled=true;}///(視認性向上目的のprivateな補助関数)プロンプトを除く現在行の取り出しprivatestringGetCommand(){return(Text.Substring(Text.Length-GetColumn()).Substring(Prompt.Length));}///(視認性向上目的のprivateな補助関数)現在のカーソルのカラム位置を返すprivateintGetColumn(){return(SelectionStart-Text.LastIndexOf(CR)-1);}///(視認性向上目的のprivateな補助関数)プロンプトを指定色で表示するprivatevoidShowPrompt(){SelectionColor=PromptColor;AppendText(Prompt);SelectionColor=CommandColor;}}}

 

この時点でビルド(及び実行)を行うと、RichTextBoxとはかなり異なる動きをする事を確認できると思います。但しこの時点ではまだプロンプトも表示されませんし、何を入力しても何も表示されません。

ShellTextBoxが発する独自イベントを受け取る

ShellTextBoxがEnterキーを押下された時に発生するイベントは誰が(どのクラスもインスタンスが)受け取っても構いません。一般的にはShellTextBoxを生成したCslWin.csが受け取る事が多いですが、ここではMainForm.csにコードを追加し、MainForm.csが受け取る例を紹介します。

手順1.

ShellTextBoxを利用には、MainForm.csに下記の一行の追加が必要です。最初のプロンプトはこの関数が呼ばれた時に表示されます。

MainForm.cs
CslWin.Shell.Initialize("PROMPT>",Color.DarkViolet,Color.Green,Color.Red);

引数の意味は以下の通りです。

意味省略の可否
第1引数Stringプロンプト文字列省略不可
第2引数Colorプロンプトの色省略時はTextCororプロパティの色
第3引数Color入力文字列の色省略時はTextCororプロパティの色
第4引数Color警告色省略時はColor.Red(赤色)
警告色の意味は手順2を参照

上記1行を追記すると、"CslWin.Shellはアクセスできない保護レベルになっています"のエラーが発生しますので、ClsWin.Designer.csの末尾に自動生成されたShellの定義をprivateからpublicに変更します。これでプロンプトが出現し、それに対してコマンドを入力できる事を確認でます。

ClsWin.Designer.cs
// private ShellTextBox Shell;publicShellTextBoxShell;
手順2.

ShellTextBoxが発する独自イベントを受け取る宣言と、そのイベントが発生した時の処理をMainForm.csの末尾に追加します。

MainForm.cs
///Shellに入力があれば、本クラス内の OnShell() をコールする様に設定CslWin.Shell.KeyEnter+=newShellTextBox.ShellEventHandler(OnShell);///Enterキーダウン時に発生する独自イベントの処理///現時点では何を入力しても、入力文字列+" : Unknown command"と表示するvoidOnShell(objectsender,ShellEventArgse){e.Result=e.Command+" : Unknown command";}

OnShell()はShellTextBox内でEnterキーを押下した時に呼ばれます。この時にe.CommandにプロンプトからEnterキーまでに入力したコマンド文字列(プロンプトとEnterキーは含まない、空文の場合もある)が格納されています。またOnShell()内で任意の文字列をe.Resultに設定するとそれがOnShell()終了後にShellTextBoxに表示されます。

上記のOnShell()は単に入力文字列に、" : Unknown command"を付加したものを出力するだけの処理を行っています。出力文字の色はTextColorプロパティと同色になりますが、出力文字列の先頭文字が"!"の場合は、Initialize()の第4引数で指定した警告色で"!"を除く文字列が表示されます。

ShellEventArgsのメンバ
メンバ名意味
commandString入力された文字列がセットされる
resultString出力したい文字列をセットする
先頭文字が"!"の場合は文字色が変わる
(先頭文字の"!"は表示されない)
その他継承元であるSystem.EventArgsと同じ

終わりに

以上で、本記事冒頭で紹介した「何を入力してもUnKnownは返るアプリ」の完成です。

WS2019_000697.jpg


Viewing all articles
Browse latest Browse all 9743

Trending Articles