1

私は Windows フォームとユーザー コントロールを操作しようとしていますが、これまでのところ頭痛の種にすぎません。フォームまたはコントロールを静的にすることはできません。デザイナーがそれを好まないためです。フォームとコントロールでシングルトンを使用すると、デザイナーはまだエラーをスローします。

私のフォームメイン:

public partial class FormMain : Form
{
    private static FormMain inst;

    public static FormMain Instance
    {
        get
        {
            if (inst == null || inst.IsDisposed)
                inst = new FormMain();
            return inst;
        }
    }

    private FormMain()
    {
        inst = this;
        InitializeComponent();
    }

MainScreen.cs:

public partial class MainScreen : UserControl
{
    private static MainScreen inst;

    public static MainScreen Instance
    {
        get
        {
            if (inst == null || inst.IsDisposed)
                inst = new MainScreen();
            return inst;
        }
    }

    private MainScreen()
    {
        inst = this;
        InitializeComponent();
    }

MainScreen のコンストラクターが public の場合、プログラムは実行されますが、それを private に変更すると、FormMain.Designer.cs で「保護レベルが原因で 'Adventurers_of_Wintercrest.UserControls.MainScreen.MainScreen()' にアクセスできません」というエラーが表示されるようになりました。 . 次の行を指しています。

        this.controlMainScreen = new Adventurers_of_Wintercrest.UserControls.MainScreen();

これはデザイナーがデフォルトで作るクラスのインスタンスだと思います。デザイナーを辞めるべきですか?または、これを回避する方法はありますか?または、シングルトンを使用せずにクラス プロパティにアクセスできるようにする別の方法はありますか (フォームまたはコントロールを静的にすることができないため)。どんな助けでも大歓迎です。

4

3 に答える 3

3

インスタンス化されたフォームのパブリック プロパティにアクセスする場合は、各フォームの各インスタンスへの参照を保持する必要があります。

1 つの方法は、フォームの種類ごとに静的変数を持つクラスを用意することです。

class FormReferenceHolder
{
    public static Form1 form1;
    public static Form2 form2;
}

このようにして、フォームをインスタンス化するたびに静的変数を設定すると、プログラムのどこからでもその変数にアクセスできます。これをさらに一歩進めて、フォームがまだ存在しない場合はフォームを設定するプロパティを使用できます。

class FormReferenceHolder
{
    private static Form1 form1;
    public static Form1 Form1
    {
        get
        {
            if (form1 == null) form1 = new Form1(); 
            return form1 ;
        }
    }
}

...

static void Main()
{
    Application.Run(FormReferenceHolder.Form1 );
}
于 2013-05-10T17:04:51.687 に答える
1

これについての以前の質問に答えたと思いますが、それがあなたがこの道を歩み始めたきっかけのようです。最初のポイントは、私がこのパターンを特に推奨したわけではなく、ソフトウェア開発者がスコープを管理する方法についてもっと教えようとしているということです。

とはいえ、あなたが直面している問題は克服できないものではありません。たとえば、設計時ではなく実行時に例外をスローしてパブリック コンストラクターを妨害し、Program.cs を変更してフォームを手動で構築する代わりに静的インスタンスを使用することができます。

しかし。

他の質問で言ったように、アーキテクチャを変更して、そもそもライブラリ コードで GUI を直接操作する必要がないようにすることをお勧めします。

これを行うには、GUI が新しいデータ (単純な関数) が必要だと判断したときにライブラリに質問するか、何かを変更する必要があるときに GUI に通知するようにします。どちらの方法も、ライブラリにラベルを直接いじらせるよりはましです。

開始するのに適した場所は、以前の回答でほのめかした MVC (モデル ビュー コントローラー) アーキテクチャのようなものです。ただし、上位レベルのプログラム構造がどのように見えるかについて、もう少し詳細に説明するのが最善かもしれません。システムで使用している主なクラスは何ですか (これまでに言及したものだけではありません)。それぞれの主な責任は何ですか。また、それぞれがどこに住んでいますか? 次に、推奨事項をもう少し具体的にすることができます。

編集

そこで、あなたのコメントに基づいて、可能な代替アーキテクチャの簡単なデモをモックアップしました。

私のプロジェクトには次のものがあります。

  • FormMain (Form)
  • TitleScreen (UserControl)
  • InGameMenu (UserControl)
  • MainScreen (UserControl)
  • GameController (Class)
  • GameModel (Class)

Date今のところandは使用しませんでしたLoadSave

FormMainそれぞれのインスタンスがUserControlドロップされているだけです。特別なコードはありません。

GameControllermodel を操作することでユーザー入力に応答するシングルトンです (このパターンを既に使用しようとしており、実際に動作するバージョンを使用してみると役立つと思います) 。注: モデルを 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 コンテキストで他のものと同じように簡単に使用できます。

これは、私や他の人が説明しようとしてきたアーキテクチャの原則のいくつかを明確にしていますか?

于 2013-05-10T16:58:36.627 に答える
0

本質的に問題は、アプリケーションが実行されると、メインのフォーム ウィンドウをインスタンス化しようとすることです。しかし、Singleton パターンを使用することで、本質的にアプリケーションがそれを行うことを禁止しています。

サンプル コードをご覧ください: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.aspx

特に次のセクションに注目してください。

[STAThread]
public static void Main()
{
    // Start the application.
    Application.Run(new Form1());
}

プログラムが をインスタンス化しようとしていることに注意してくださいForm1。あなたのコードは、いや、コンストラクターをプライベートとしてマークしているので、私は本当にそれを望んでいないと言います(同じことが静的フォームにも当てはまります)。しかし、それは Windows フォームが機能するはずの方法に反しています。フォーム ウィンドウが必要な場合singletonは、これ以上作成しないでください。そのような単純な。

于 2013-05-10T16:29:04.173 に答える