C# フォームを動的に作成して実行したい

タグの編集
投稿者 ケンケン  (社会人) 投稿日時 2023/5/24 11:03:47
C#
  画面(form)を動的に作成して、実行したいのですが
 どの様にコーディングをしれば宜しいのでしょうか?
 何方か分かる方ご教授お願いいたします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/5/24 11:31:12
デザイン時に、Form1.cs と一緒に Form1.designer.cs が生成されますよね?
それを真似て、各種コントロールを動的に Controls.Add してみては如何でしょう。
投稿者 ケンケン  (社会人) 投稿日時 2023/5/24 12:22:06
回答ありがとうございます。

少しめんどくさくて
部署単位で表示する画面が違います。
部署1
  売上画面 ( pgm0001)
  仕入画面 ( pgm0002)
部署2
  マスタメンテ画面 (pgm0003)

 pgmxxxx が実行したい画面のプログラムです。
 
 プログラムを動的に実行したいときはどの様にコーディングすれば宜しいのでしょうか?
 
 よろしくお願いいたします。 
 
   
 
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/5/24 18:17:33
動的に画面を作りたいという質問だと思ったのですが、
既に定義済みの画面を呼び出したいという質問だったのでしょうか。


> pgmxxxx が実行したい画面のプログラムです。
ここでいう「画面のプログラム」とは、何を指していますか?

pgm0001.exe というファイルを呼び出したいという意味であるのなら、
Process.Start メソッドを使います。


「public partial class pgm0001 : Form」な画面クラスを呼び出したいのなら、
 Form 変数名 = new pgm0001();
 変数名.Show(this);
などと書けますね。

"pgm0001" の部分などを文字列で指定したいなら、
Activator.CreateInstance メソッドを併用すると良いでしょう。
別の DLL で定義したフォームを呼び出すなんてこともできます。
https://gya-ia.hatenablog.com/entry/20111028/1319730965


あるいは、 = new pgm0001("引数1", "引数2"); のように
画面によっては追加のコンストラクタ引数が必要なケースなどでは、
あらかじめ static class Program などに
 public static Dictionary<string, Func<Form>> Creator = new Dictionary<string, Func<Form>>()
 {
  { "売上画面", () => new pgm0001("引数1", "引数2") },
  { "仕入画面", () => new pgm0002() },
 };
などの、「フォームを生成するためのデリゲート」を準備しておき、それを利用するという手も。


> 部署単位で表示する画面が違います。
それが「プログラムを動的に実行したい」という点に対して
どのように関わってくるのかが読み取れませんでした。


部署1を選択したときは pgm0001 と pgm0002 の 2 つが同時に実行され、
部署2を選択したときは pgm0003 だけが実行される、ということでしょうか。
部署によって画面数が違うようなので、階層管理の方法を相談したいという話?

あるいは最初にログイン画面があって、
部署1に所属しているユーザーなら
 [売上画面] ボタン ( pgm0001 を表示)と
 [仕入画面] ボタン ( pgm0002 を表示)
の 2 つが並んでいるメニュー画面が現れるが、
部署2に所属しているユーザーでログインした場合は、
 [マスタメンテ画面] ボタン ( pgm0003 を表示)
だけのメニュー画面を表示したい…といったことですか?


何を知りたいのかが、今一つ分からないです。
投稿者 ケンケン  (社会人) 投稿日時 2023/5/25 06:40:32

  回答有り難う。

 [マスタメンテ画面] ボタン ( pgm0003 を表示)
だけのメニュー画面を表示したい…といったことですか?

 ↑ ボタンが表示されます。
   部署によってボタンのメニュー画面が異なります。

   ログオン時
   
   部署1にはボタンが2つ表示されます。
   部署2にはボタンが1つ表示されます。
    
       そのとおりです。
  
    よろしくお願いいたします。  
 
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/5/25 09:42:47
> そのとおりです。
> よろしくお願いいたします。

画面がその構成であるとして、
> フォームを動的に作成して実行
という質問とどう繋がるのかが、やっぱりわかりません。C# のバージョンはおろか、
そもそも Web アプリなのか、WPF なのか WinForms なのか MAUI なのかさえ不明ですし…。


とりあえず Windows Forms だと仮定してみますが、話を聞く限りではデザイン時にあらかじめ
メニュー画面上に、各画面に遷移するためのボタンをすべての画面の分だけ用意しておき、
 button1.Enabled = false;
 button2.Visible = false;
などとして、ログイン時の部署ごとの権限にあわせて、各遷移ボタンの
使用可能/使用不可、表示/非表示を切り替えてしまえば済むように思います。
(必要なら Location や Text や文字色も変更します)


あるいはボタンも動的に生成したいという話なら、先に回答した
>> 動的に Controls.Add
を使えば良い話ですよね。



ボタン自体はそのままにして、Click 時の遷移先の画面を切り替えたいだけなら
 private void button1_Click(object sender, EventArgs e)
 {
  var childForm
   = (ログイン部署 == "部署1") ? new pgm0001()
   :  (ログイン部署 == "部署2") ? new pgm0003()
   : default(Form);
  if (childForm != null)
  {
   using(childForm) { childForm.ShowDialog(this); }
  }
 }
のように、条件分岐でどの画面のインスタンスを生成するのかを変更すれば良いかと。


この時、先の回答のように、
>> public static Dictionary<string, Func<Form>> Creator
などを用意しておけば、各ボタンの Text に画面名をセットしておくことで
 // このイベントハンドラは、すべての遷移 Button から共通して呼ばれる
 private void buttons_Click(object sender, EventArgs e)
 {
  string caption = ((Button)sender).Text;  // ボタンのタイトルを取得
  if (Program.Creator.TryGetValue(caption, out Form childForm))
  {
   using (childForm)
   {
    childForm.ShowDialog(this);
   }
  }
 }
のように書けるでしょう。
部署ごとの判断が固定的なら、それも Dictionary などで階層管理するという手もありますね
(たとえば Dictionary の Key をタプルにするとか、Dictionary の Value を子階層の Dictionary にするとか)


権限判断の条件がもう少し複雑になるような場合は、その判断を行うための
 public Form GetFormInstance(string 部署名, int 画面連番)
な感じのメソッドを自分で用意しておくのも良いでしょう。
その場合は、そのメソッドを用いて
 //var childForm = GetFormInstance("部署1", 1); // = new pgm0001(); に相当
 //var childForm = GetFormInstance("部署1", 2); // = new pgm0002(); に相当
 //var childForm = GetFormInstance("部署2", 1); // = new pgm0003(); に相当
 var childForm = GetFormInstance(loginAccount.Department, buttonIndex);
 if (childForm != null)
 {
  using(childForm)
  {
   childForm.ShowDialog(this);
  }
 }
などとして呼べる形にもできますね。

上記はいずれも ShowDialog によるモーダル呼び出しにしていますが、
using を外したうえで、Show によるモードレス呼び出しにもできます。



あるいはそうした権限情報をデータベース等で保持しているのなら、
呼び出したい画面の完全修飾名(名前空間を含めたクラス名のこと)をデータベースから取得して、
 Type formType = Type.GetType(画面の完全修飾名, true);
 var childForm = (Form)Activator.CreateInstance(formType);
 using(childForm)
 {
  childForm.ShowDialog(this);
 }
などと書くこともできます。これは先に回答した
>> "pgm0001" の部分などを文字列で指定したいなら、
>> Activator.CreateInstance メソッドを併用
のことです。Activator.CreateInstance の使用例はこちら。
https://atmarkit.itmedia.co.jp/ait/articles/0512/09/news136.html

なお、自プロジェクト上にある画面を呼ぶ場合は上記コードでいけますが、
他の DLL ファイルや EXE ファイル上で定義されている画面を動的に呼び出す場合は、
前回の回答で紹介した Assembly.LoadFrom メソッドや、
下記(VB 版ですが)で使われている Assembly.LoadFile メソッドを併用します。
http://vbnettips.blog.shinobi.jp/_form/%E4%BB%96%E3%81%AEexe%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%EF%BC%88ac
http://vbnettips.blog.shinobi.jp/_form/%E4%BB%96%E3%81%AEexe%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%9D%E3%81%AE%EF%BC%92
投稿者 ケンケン  (社会人) 投稿日時 2023/5/25 12:26:18
回答ありがとうございます。
色々と参考してコードを作りたいと思います。