2013-05-10 96 views
1

我正在嘗試使用Windows窗體和用戶控件,因此它一直只是頭疼。我無法將表單或控件設置爲靜態,因爲設計人員不喜歡這樣,當我在窗體和控件上使用Singleton時,設計人員仍然會向我發出錯誤。Windows窗體,設計器和Singleton

我FormMain:

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的構造函數是公共的程序運行,但是當我將其更改爲私人我現在得到FormMain錯誤.Designer.cs表示「'Adventurers_of_Wintercrest.UserControls.MainScreen.MainScreen()'由於其保護級別而無法訪問」。它指向這條線:

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

我認爲這是設計師默認設計的類的實例。我應該拋棄設計師嗎?或者有沒有辦法解決這個問題?還是有另一種方法來使類屬性可以訪問,而不使用Singleton(因爲我似乎無法使表單或控件靜態)?任何幫助將不勝感激。

+4

爲什麼你需要控制singletone。這完全不合邏輯,並沒有計劃以這種方式使用 – 2013-05-10 16:21:00

+0

正如我在下面的評論中提到的,我真的只是尋找一種方法來訪問我的控件的公共屬性和其他控件的窗體。到目前爲止,我還沒有找到一個好方法。 – 2013-05-10 16:41:02

+1

很好,你不能以不錯的方式做壞事=)。控制是已完成功能的一部分,它不應該依賴(甚至不知道是否存在)任何外部控制。如果您需要在控件之間實現一些通信,請通過事件(實現您的自定義事件)執行並以主窗體的形式訂閱它們。 – 2013-05-10 16:54:05

回答

3

如果您想訪問實例化表單的公共屬性,則需要對每個表單的每個實例保留一個引用。

一種方法是有一個類,每種類型格式的靜態變量:

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); 
} 
+0

這會爲UserControls工作嗎? – 2013-05-10 17:12:08

+0

我這樣做並從FormMain中刪除所有控件。然後,我將TitleScreen的一個實例添加到ReferenceHolder類中,但我不知道如何在我的表單上顯示它。我曾嘗試在FormMain構造函數中使用Show,Visible和Enable,但它仍未出現。 – 2013-05-10 17:33:31

+0

即使你得到這個工作,它不是我會建議的體系結構。再一次,你不希望你的庫代碼搞砸你看我的例子在我編輯的答案,我認爲是一個更好的策略,爲你的目的。 – 2013-05-10 18:50:51

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窗體,就不要再做了。就那麼簡單。

+0

我覺得辛格爾頓可能並不是最好的選擇,但我希望能夠從我的窗體和其他控件中的用戶控件訪問公共屬性。什麼是最好的方式去做呢? – 2013-05-10 16:37:06

+0

所以最簡單的答案就是你需要創建一個應用程序'Model'。這在WPF中很容易實現,因爲它是爲此而構建的,但它也可以在win-form中完成。實質上,您將所有業務邏輯存儲在構成「模型」的類或系列類中。該模型管理應用程序狀態,然後在模型狀態更改時向UI發送通知。關鍵在於將UI更新與實際應用程序狀態分開。並將按鍵按鍵傳遞給模型,而不是在UI中處理它們。 – sircodesalot 2013-05-10 17:25:02

+0

因此,例如,您可能有一個處理網絡連接的單例類,也可能是另一個用於處理數據庫事務的單例類。當這些類的狀態發生變化時,將更新推送到UI。當用戶與UI進行交互時,您可以將這些通知返回給模型進行處理。你不能創建單身形式的原因,不是因爲單身人士等是真正的「模型」概念,而不是「視圖/ UI」概念。視圖應該只響應模型中的更新,並將UI命令發送回模型。 Google MVC/MVVM/MVP。 – sircodesalot 2013-05-10 17:32:15

1

我想我回答了一個關於這個問題的前一個問題,看起來這是什麼讓你開始沿着這條路線。第一點是我沒有特別推薦這種模式,只是想教你更多關於軟件開發人員如何管理範圍。

這就是說,你所面臨的問題並非無法克服。例如,您可以在運行時拋出一個異常而不是在設計時拋出一個公共構造函數,並修改Program.cs以使用靜態實例,而不是手動構建表單。

但是。

正如我在其他問題中所說的,更好的選擇是更改體系結構,以便您不需要庫代碼即可直接操作GUI。

您可以通過僅在GUI認爲需要新數據(簡單函數)時詢問庫問題,或者在需要更改某些內容時通知GUI來做到這一點。任何一種方法都會比直接使用庫提供標籤更好。

開始的好地方就像MVC(模型 - 視圖 - 控制器)體系結構,我在之前的回答中提到了這一點。不過,最好給我們一個關於你的高級程序結構現在看起來更詳細一點的想法。你在系統中使用的主要類是什麼(不僅僅是你迄今爲止提到的那些)?每個人的主要責任是什麼,每個人的生活在哪裏?那麼我們的建議可能會更具體。

編輯

所以,我嘲笑了一個可能的替代架構的快速演示,根據您的評論。

我在我的項目如下:

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

現在我沒有使用DateLoadSave

FormMain只是簡單地在每個UserControl上放置一個實例。沒有特殊的代碼。

GameController是單(因爲你嘗試已經使用這種模式,我認爲這將是有益的,您可以嘗試使用它的一個工作版本),其通過操縱模型響應用戶輸入。請注意:您不直接從GUI(這是模型視圖控制器的視圖部分)操縱模型。它暴露的GameModel一個實例,並有一堆的,讓你進行遊戲操作,如加載/保存方法,結束了轉彎等

GameModel是你的所有遊戲狀態下進行保存。在這種情況下,這只是一個日期和一個回合計數器(就好像這將是一個回合制遊戲)。日期是一個字符串(在我的遊戲世界中,日期以「Eschaton 23,3834.4」格式顯示),每一輪都是一天。

TitleScreen和InGameMenu每個只有一個按鈕,爲清晰起見。理論上(不是實現),TitleScreen讓你開始一個新的遊戲,InGameMenu讓你加載一個現有的遊戲。

所以通過介紹,這裏是代碼。

GameModel:

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)"; 
    } 
} 

注意事項:你的模型有一個事件(在這種情況下,只需鍵入事件處理程序的一個,你以後可以創建更多的表現類型的事件,但讓我們從簡單的開始)稱爲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可能需要在遊戲狀態下執行的所有高級操作:加載或保存遊戲,結束轉彎等。

現在休息很簡單。

TitleScreen:

public partial class TitleScreen : UserControl 
{ 
    public TitleScreen() 
    { 
     InitializeComponent(); 
    } 

    private void btnLoadNew(object sender, EventArgs e) 
    { 
     GameController.Instance.LoadNewGame(); 
    } 
} 

InGameMenu:

public partial class InGameMenu : UserControl 
{ 
    public InGameMenu() 
    { 
     InitializeComponent(); 
    } 

    private void btnLoadSaved_Click(object sender, EventArgs e) 
    { 
     GameController.Instance.LoadSavedGame("test"); 
    } 
} 

注意這兩個怎麼做什麼,但在控制器上調用方法。簡單。

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。在Windows Phone或Mono上下文中,您可以輕鬆使用這兩個類作爲其他任何內容。

這是否闡明瞭我和其他人試圖解釋的一些架構原則?

+0

我現在所有的只是調用FormMain的一個新實例的Program.cs。 FormMain包含UserControls的TitleScreen,MainScreen和InGameMenu。所有都是由設計師建造我有班'日期'和'SaveLoad'。在MainScreen中我有一個Label'lblDate'。當我從TitleScreen或InGameMenu加載遊戲時,我需要相應地修改lblDate的Text屬性。不幸的是我還沒有找到辦法做到這一點。我看到Oleksandr建議使用Events,但在這種情況下,我不知道如何。 – 2013-05-10 17:09:06

+0

好的,這有幫助。所以,當一個新遊戲被加載時,lblDate會被設置,這可能是因爲MainScreen被加載(我猜是來自TitleScreen?),或者是通過遊戲內菜單,用戶將用它來加載不同的遊戲或保存的版本一邊玩遊戲或其他任何東西。我有這個權利嗎? – 2013-05-10 17:33:14

相關問題