これについての以前の質問に答えたと思いますが、それがあなたがこの道を歩み始めたきっかけのようです。最初のポイントは、私がこのパターンを特に推奨したわけではなく、ソフトウェア開発者がスコープを管理する方法についてもっと教えようとしているということです。
とはいえ、あなたが直面している問題は克服できないものではありません。たとえば、設計時ではなく実行時に例外をスローしてパブリック コンストラクターを妨害し、Program.cs を変更してフォームを手動で構築する代わりに静的インスタンスを使用することができます。
しかし。
他の質問で言ったように、アーキテクチャを変更して、そもそもライブラリ コードで GUI を直接操作する必要がないようにすることをお勧めします。
これを行うには、GUI が新しいデータ (単純な関数) が必要だと判断したときにライブラリに質問するか、何かを変更する必要があるときに GUI に通知するようにします。どちらの方法も、ライブラリにラベルを直接いじらせるよりはましです。
開始するのに適した場所は、以前の回答でほのめかした MVC (モデル ビュー コントローラー) アーキテクチャのようなものです。ただし、上位レベルのプログラム構造がどのように見えるかについて、もう少し詳細に説明するのが最善かもしれません。システムで使用している主なクラスは何ですか (これまでに言及したものだけではありません)。それぞれの主な責任は何ですか。また、それぞれがどこに住んでいますか? 次に、推奨事項をもう少し具体的にすることができます。
編集
そこで、あなたのコメントに基づいて、可能な代替アーキテクチャの簡単なデモをモックアップしました。
私のプロジェクトには次のものがあります。
FormMain (Form)
TitleScreen (UserControl)
InGameMenu (UserControl)
MainScreen (UserControl)
GameController (Class)
GameModel (Class)
Date
今のところandは使用しませんでしたLoadSave
。
FormMain
それぞれのインスタンスがUserControl
ドロップされているだけです。特別なコードはありません。
GameController
model を操作することでユーザー入力に応答するシングルトンです (このパターンを既に使用しようとしており、実際に動作するバージョンを使用してみると役立つと思います) 。注: モデルを GUI (モデル ビュー コントローラーのビュー部分) から直接操作しないでください。のインスタンスを公開し、GameModel
読み込み/保存、ターンの終了などのゲーム アクションを実行できる一連のメソッドを備えています。
GameModel
すべてのゲームの状態が保存される場所です。この場合、それはただの日付とターン カウンターです (あたかもこれがターン ベースのゲームになるかのように)。日付は文字列 (私のゲームの世界では、日付は "Eschaton 23, 3834.4" の形式で表示されます) であり、各ターンは 1 日です。
わかりやすくするために、TitleScreen と InGameMenu にはそれぞれ 1 つのボタンしかありません。理論上 (実装ではありません)、TitleScreen を使用すると新しいゲームを開始でき、InGameMenu を使用すると既存のゲームをロードできます。
紹介はここまでにして、ここにコードを示します。
ゲームモデル:
public class GameModel
{
string displayDate = "Eschaton 23, 3834.4 (default value for illustration, never actually used)";
public GameModel()
{
// Initialize to 0 and then increment immediately. This is a hack to start on turn 1 and to have the game
// date be initialized to day 1.
incrementableDayNumber = 0;
IncrementDate();
}
public void PretendToLoadAGame(string gameDate)
{
DisplayDate = gameDate;
incrementableDayNumber = 1;
}
public string DisplayDate
{
get { return displayDate; }
set
{
// set the internal value
displayDate = value;
// notify the View of the change in Date
if (DateChanged != null)
DateChanged(this, EventArgs.Empty);
}
}
public event EventHandler DateChanged;
// use similar techniques to handle other properties, like
int incrementableDayNumber;
public void IncrementDate()
{
incrementableDayNumber++;
DisplayDate = "Eschaton " + incrementableDayNumber + ", 9994.9 (from turn end)";
}
}
注意すべき点: モデルには というイベントがあります (この場合は EventHandler 型の 1 つにすぎません。後でより表現力豊かな型のイベントを作成できますが、簡単に始めましょう) と呼ばれDateChanged
ます。これは、変更されるたびに発生しDisplayDate
ます。プロパティ定義を見ると、それがどのように発生するかがわかります。set
アクセサ (GUI から呼び出すことはありません) は、誰かがリッスンしている場合にイベントを発生させます。GameController
また、必要に応じて呼び出される (GUI ではない)ゲームの状態とメソッドを格納するための内部フィールドもあります。
GameController は次のようになります。
public class GameController
{
private static GameController instance;
public static GameController Instance
{
get
{
if (instance == null)
instance = new GameController();
return instance;
}
}
private GameController()
{
Model = new GameModel();
}
public void LoadSavedGame(string file)
{
// set all the state as saved from file. Since this could involve initialization
// code that could be shared with LoadNewGame, for instance, you could move this logic
// to a method on the model. Lots of options, as usual in software development.
Model.PretendToLoadAGame("Eschaton 93, 9776.9 (Debug: LoadSavedGame)");
}
public void LoadNewGame()
{
Model.PretendToLoadAGame("Eschaton 12, 9772.3 (Debug: LoadNewGame)");
}
public void SaveGame()
{
// to do
}
// Increment the date
public void EndTurn()
{
Model.IncrementDate();
}
public GameModel Model
{
get;
private set;
}
}
上部にシングルトンの実装が表示されます。次に、常にモデルが存在することを確認するコンストラクターと、ゲームをロードおよび保存するためのメソッドが続きます。(この場合GameModel
、新しいゲームがロードされたときでも のインスタンスは変更しません。その理由は、GameModel
イベントがあり、リスナーがこの単純なサンプル コードでそれらを配線解除して再配線する必要がないようにするためです。方法を決定できます。これらのメソッドは基本的に、ゲームの状態で GUI が実行する必要があるすべての高レベルのアクションを実装することに注意してください: ゲームのロードまたは保存、ターンの終了など。
あとは簡単です。
タイトル画面:
public partial class TitleScreen : UserControl
{
public TitleScreen()
{
InitializeComponent();
}
private void btnLoadNew(object sender, EventArgs e)
{
GameController.Instance.LoadNewGame();
}
}
ゲーム内メニュー:
public partial class InGameMenu : UserControl
{
public InGameMenu()
{
InitializeComponent();
}
private void btnLoadSaved_Click(object sender, EventArgs e)
{
GameController.Instance.LoadSavedGame("test");
}
}
これら 2 つが Controller のメソッドを呼び出すだけであることに注目してください。簡単。
public partial class MainScreen : UserControl
{
public MainScreen()
{
InitializeComponent();
GameController.Instance.Model.DateChanged += Model_DateChanged;
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Model_DateChanged(object sender, EventArgs e)
{
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Instance_CurrentGameChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void btnEndTurn_Click(object sender, EventArgs e)
{
GameController.Instance.EndTurn();
}
}
これはもう少し複雑ですが、それほどではありません。重要なのは、DateChanged
モデルのイベントを関連付けることです。このようにして、日付がインクリメントされたときに通知できます。ここでは、ボタンに別のゲーム機能 (ターン終了) も実装しました。
これを複製して実行すると、ゲームの日付がさまざまな場所で操作され、ラベルが常に適切に更新されていることがわかります。何よりも、コントローラーとモデルは実際にはビューについてまったく何も知りません。それが WinForms に基づいていることさえ知りません。これらの 2 つのクラスは、Windows Phone または Mono コンテキストで他のものと同じように簡単に使用できます。
これは、私や他の人が説明しようとしてきたアーキテクチャの原則のいくつかを明確にしていますか?