7

मैं स्क्रैच से WinForms एप्लिकेशन को फिर से लिखने की प्रक्रिया में हूं (और यह में है WinForms होने के लिए, जितना मैं WPF और MVVM का उपयोग करना चाहता हूं)। ऐसा करने के लिए मैंने एमवीसी पैटर्न का उपयोग करने और निर्भरता इंजेक्शन (डीआई) का उपयोग करने का प्रयास किया है जहां टेस्टेबिलिटी, रखरखाव इत्यादि को बढ़ाने के लिए संभव होनिर्भरता इंजेक्शन के साथ WinForms एमवीसी

मेरी समस्या एमवीसी और डीआई के उपयोग के साथ है। बैसिक एमवीसी पैटर्न के साथ, नियंत्रक के पास दृश्य तक पहुंच होनी चाहिए और दृश्य में नियंत्रक तक पहुंच होनी चाहिए (WinForms उदाहरण के लिए here देखें); यह सीटीओआर इंजेक्शन का उपयोग करते समय एक परिपत्र संदर्भ की ओर जाता है और यह मेरे प्रश्न का क्रूक्स है। सबसे पहले मेरी कोड पर विचार करें

Program.cs (मुख्य प्रवेश WinForms आवेदन के बिंदु):

static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath()); 
     Log.LogHandler = fileLogHandler; 
     Log.Trace("Program.Main(): Logging initialized"); 

     CompositionRoot.Initialize(new DependencyModule()); 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(CompositionRoot.Resolve<ApplicationShellView>()); 
    } 
} 

DependencyModule.cs

public class DependencyModule : NinjectModule 
{ 
    public override void Load() 
    { 
     Bind<IApplicationShellView>().To<ApplicationShellView>(); 

     Bind<IDocumentController>().To<SpreadsheetController>(); 
     Bind<ISpreadsheetView>().To<SpreadsheetView>(); 
    } 
} 

CompositionRoot.cs

public class CompositionRoot 
{ 
    private static IKernel ninjectKernel; 

    public static void Initialize(INinjectModule module) 
    { 
     ninjectKernel = new StandardKernel(module); 
    } 

    public static T Resolve<T>() 
    { 
     return ninjectKernel.Get<T>(); 
    } 

    public static IEnumerable<T> ResolveAll<T>() 
    { 
     return ninjectKernel.GetAll<T>(); 
    } 
} 

ApplicationShellView.cs (आवेदन का मुख्य रूप)

public partial class ApplicationShellView : C1RibbonForm, IApplicationShellView 
{ 
    private ApplicationShellController controller; 

    public ApplicationShellView() 
    { 
     this.controller = new ApplicationShellController(this); 
     InitializeComponent(); 
     InitializeView(); 
    } 

    public void InitializeView() 
    { 
     dockPanel.Extender.FloatWindowFactory = new CustomFloatWindowFactory(); 
     dockPanel.Theme = vS2012LightTheme; 
    } 

    private void ribbonButtonTest_Click(object sender, EventArgs e) 
    { 
     controller.OpenNewSpreadsheet(); 
    } 

    public DockPanel DockPanel 
    { 
     get { return dockPanel; } 
    } 
} 

कहाँ:

public interface IApplicationShellView 
{ 
    void InitializeView(); 

    DockPanel DockPanel { get; } 
} 

ApplicationShellController.cs

public class ApplicationShellController 
{ 
    private IApplicationShellView shellView; 

    [Inject] 
    public ApplicationShellController(IApplicationShellView view) 
    { 
     this.shellView = view; 
    } 

    public void OpenNewSpreadsheet(DockState dockState = DockState.Document) 
    { 
     SpreadsheetController controller = (SpreadsheetController)GetDocumentController("new.xlsx"); 
     SpreadsheetView view = (SpreadsheetView)controller.New("new.xlsx"); 
     view.Show(shellView.DockPanel, dockState); 
    } 

    private IDocumentController GetDocumentController(string path) 
    { 
     return return CompositionRoot.ResolveAll<IDocumentController>() 
      .SingleOrDefault(provider => provider.Handles(path)); 
    } 

    public IApplicationShellView ShellView { get { return shellView; } } 
} 

SpreadsheetContro ller.cs

public class SpreadsheetController : IDocumentController 
{ 
    private ISpreadsheetView view; 

    public SpreadsheetController(ISpreadsheetView view) 
    { 
     this.view = view; 
     this.view.SetController(this); 
    } 

    public bool Handles(string path) 
    { 
     string extension = Path.GetExtension(path); 
     if (!String.IsNullOrEmpty(extension)) 
     { 
      if (FileTypes.Any(ft => ft.FileExtension.CompareNoCase(extension))) 
       return true; 
     } 
     return false; 
    } 

    public void SetViewActive(bool isActive) 
    { 
     ((SpreadsheetView)view).ShowIcon = isActive; 
    } 

    public IDocumentView New(string fileName) 
    { 
     // Opens a new file correctly. 
    } 

    public IDocumentView Open(string path) 
    { 
     // Opens an Excel file correctly. 
    } 

    public IEnumerable<DocumentFileType> FileTypes 
    { 
     get 
     { 
      return new List<DocumentFileType>() 
      { 
       new DocumentFileType("CSV", ".csv"), 
       new DocumentFileType("Excel", ".xls"), 
       new DocumentFileType("Excel10", ".xlsx") 
      }; 
     } 
    } 
} 

कहाँ कार्यान्वित इंटरफेस है:

public interface IDocumentController 
{ 
    bool Handles(string path); 

    void SetViewActive(bool isActive); 

    IDocumentView New(string fileName); 

    IDocumentView Open(string path); 

    IEnumerable<DocumentFileType> FileTypes { get; } 
} 

अब दृश्य इस नियंत्रक के साथ ascociated है:

public partial class SpreadsheetView : DockContent, ISpreadsheetView 
{ 
    private IDocumentController controller; 

    public SpreadsheetView() 
    { 
     InitializeComponent(); 
    } 

    private void SpreadsheetView_Activated(object sender, EventArgs e) 
    { 
     controller.SetViewActive(true); 
    } 

    private void SpreadsheetView_Deactivate(object sender, EventArgs e) 
    { 
     controller.SetViewActive(false); 
    } 

    public void SetController(IDocumentController controller) 
    { 
     this.controller = controller; 
     Log.Trace("SpreadsheetView.SetController(): Controller set successfully"); 
    } 

    public string DisplayName 
    { 
     get { return Text; } 
     set { Text = value; } 
    } 

    public WorkbookView WorkbookView 
    { 
     get { return workbookView; } 
     set { workbookView = value; } 
    } 

    public bool StatusBarVisible 
    { 
     get { return statusStrip.Visible; } 
     set { statusStrip.Visible = value; } 
    } 

    public string StatusMessage 
    { 
     get { return statusLabelMessage.Text; } 
     set { statusLabelMessage.Text = value; } 
    } 
} 

दृश्य इंटरफेस हैं:

public interface ISpreadsheetView : IDocumentView 
{ 
    WorkbookView WorkbookView { get; set; } 
} 

और:

public interface IDocumentView 
{ 
    void SetController(IDocumentController controller); 

    string DisplayName { get; set; } 

    bool StatusBarVisible { get; set; } 
} 

मैं डि और Ninject के लिए नया हूँ तो मैं दो प्रश्न हैं:

  1. मैं अपने आप को SpreadsheetController में this.view.SetController(this); का उपयोग कर रोका जा सकता है कैसे, यहाँ यह लगता है जैसे मैं आईओसी का उपयोग करना चाहिए कंटेनर, लेकिन शुद्ध सीटीआर-इंजेक्शन का उपयोग सर्कुलर संदर्भ और StackOverflowException की ओर जाता है। क्या यह शुद्ध डीआई का उपयोग करके किया जा सकता है?

क्योंकि मेरे पास WPF (या एएसपी के साथ बाध्यकारी ढांचा नहीं है।एनईटी दृश्य और नियंत्रक को पूरी तरह से जोड़ने की क्षमता), मुझे दृश्य और नियंत्रक को एक दूसरे को स्पष्ट रूप से बेनकाब करना होगा। यह सही "महसूस" नहीं करता है और मुझे लगता है कि यह निनजेक आईओसी कंटेनर के साथ संभव होना चाहिए, लेकिन मेरे पास यह स्थापित करने का अनुभव नहीं है कि यह कैसे किया जा सकता है (यदि यह कर सकता है)।

  1. क्या मेरा निंजा/डी का उपयोग यहां सही है। जिस तरह से मैं अपने CompositionRoot का उपयोग कर रहा हूं और विधि GetDocumentController(string path) सेवा लोकेटर एंटी-पैटर्न की तरह लगता है, मैं यह अधिकार कैसे बना सकता हूं?

इस समय यह कोड ठीक काम करता है, लेकिन मैं इसे सही प्राप्त करना चाहता हूं। आपके समय के लिए अत्यधिक धन्यवाद।

+2

मैंने देखा कि आपने पहले कहा था कि आप हमें एमवीवीएम और डब्ल्यूपीएफ पसंद करेंगे। खैर मैं WinForms के साथ WVVM का उपयोग करके थोड़ी देर के लिए रहा हूं और इसे एमवीसी या एमवीपी की तुलना में अधिक कुशल माना जाता है। WinForms बाइंडिंग WPF में उतनी उन्नत नहीं हैं, लेकिन मुझे लगता है कि ज्यादातर मामलों में काम काफी अच्छी तरह से पर्याप्त है। पूरी बात के साथ मेरी सहायता करने के लिए मैं WinForms के लिए DevExpress MVVM का उपयोग कर रहा हूं: https://documentation.devexpress.com/#WindowsForms/CustomDocument113955 –

उत्तर

4

मैं एक समान वास्तुकला के साथ एक परियोजना पर काम कर रहा हूं।

मुझे लगता है कि आपकी मुख्य समस्या यह है कि आपके दृश्य के ईवेंट हैंडलर सीधे नियंत्रक को कॉल करते हैं। E.g:

private void ribbonButtonTest_Click(object sender, EventArgs e) 
{ 
    controller.OpenNewSpreadsheet(); 
} 

इससे बचने का प्रयास करें। अपने नियंत्रक ऑब्जेक्ट्स को अपने एप्लिकेशन के स्वामी बनने दें। विचारों और मॉडल को "अंधेरे और बहरे" होने दें।

जब आपका विचार किसी उपयोगकर्ता कार्रवाई से मुकाबला करता है, तो बस एक और ईवेंट उठाएं। इस घटना में पंजीकरण करने और इसे संभालने के लिए नियंत्रक जिम्मेदार होने दें।आपके विचार इस तरह होने जा रहा है:

public event EventHandler<EventArgs> RibbonButtonTestClicked ; 

protected virtual void ribbonButtonTest_Click(object sender, EventArgs e) 
{ 
    var handler = RibbonButtonTestClicked; 
    if (handler != null) handler(this, EventArgs.Empty); 
} 

आप ऐसा करते हैं, तो आप दृश्य में सभी नियंत्रक संदर्भ से छुटकारा पाने के लिए सक्षम होना चाहिए। आपका नियंत्रक contstructor इस तरह दिखेगा:

[Inject] 
public ApplicationShellController(IApplicationShellView view) 
{ 
    this.shellView = view; 
    this.shellView.RibbonButtonTestClicked += this.RibbonButtonTestClicked; 
} 

जब से तुम, अब और एक दृश्य से अपना वस्तु पेड़ को हल नहीं कर सकता अपने नियंत्रक के लिए एक विधि "GetView()" जोड़ सकते हैं और अपने Program.Main() विधि बदलने के लिए:

CompositionRoot.Initialize(new DependencyModule()); 
Application.EnableVisualStyles(); 
Application.SetCompatibleTextRenderingDefault(false); 
var appCtrl = CompositionRoot.Resolve<ApplicationShellController>() 
Application.Run(appCtrl.GetView()); 
+0

मुझे अपने मुख्य खोल बंद करने में कोई समस्या है। जब मैं मुख्य विंडो बंद बटन पर क्लिक करता हूं, तो मैं वर्तमान में संबंधित विचारों में ईवेंट हैंडलर को 'आईडी दस्तावेज़' के नियंत्रकों को तार भेज रहा हूं और फिर नियंत्रक क्लोज़/टियरडाउन कर सकते हैं। इस मुद्दे के साथ यह है कि मुख्य खोल से मुझे केवल विचारों तक पहुंच है और यहां से मैं जांच करने के लिए अंतर्निहित नियंत्रकों तक नहीं पहुंच सकता कि दस्तावेज़ गंदे राज्य में हैं या नहीं। मैं विचारों को "अंधा" रखना चाहता हूं लेकिन मुझे 'view.GetController()' के अलावा नियंत्रकों तक पहुंचने का एक और तरीका नहीं दिख रहा है, फिर कुछ बुरा करें। कोई विचार? – MoonKnight

+0

@ किलरकैम, क्या आप एलीमेंटहोस्ट पर डब्ल्यूपीएफ डॉकपनेल का उपयोग कर रहे हैं? हम DevExpress WinForms DockPanel और DevExpress XtraForms का उपयोग कर रहे हैं। वे एक साथ अच्छी तरह से काम करते हैं। जब उपयोगकर्ता मुख्य विंडो बंद कर देता है, तो प्रत्येक खुले पैनल को क्लोजिंगपैनल ईवेंट मिल रहा है, मुख्य विंडो को फॉर्मक्लोसिंग ईवेंट प्राप्त हो रहा है और वे सभी एक ही CancelEventArgs तर्क का उपयोग करते हैं। तो हमारे पैनल नियंत्रक क्लोजिंगपैनल ईवेंट सुन सकते हैं और रद्द संपत्ति को झूठी में सेट कर सकते हैं, उदा। जब सहेजे गए परिवर्तन मौजूद हैं। मैं डब्ल्यूपीएफ से संबंधित समस्याओं को दूर करने में आपकी सहायता नहीं कर सकता क्योंकि मुझे डब्ल्यूपीएफ अनुभव में कमी है। – Chrigl

+0

यह WinForms है लेकिन मैं एक मुफ्त डॉकपैन सूट का उपयोग कर रहा हूं। मुझे प्रासंगिक डॉक किए गए फॉर्म के लिए 'ऑनफॉर्मक्लोसिंग' ईवेंट सुनना है और फिर संदर्भ के आधार पर अनुरोध से निपटना है (एप्लिकेशन बंद है, क्या उपयोगकर्ता केवल फॉर्म को बंद कर रहा है)। आपके उत्तर के लिए बहुत बहुत धन्यवाद लेकिन ऐसा लगता है कि आप जो कर रहे हैं वह कर रहे हैं मुझे करने की आवश्यकता होगी। मैंने एक 'दस्तावेज़ प्रबंधक' लिखा है जो नियंत्रकों को कैश करता है और दस्तावेज़ों को बंद करने और खोलने का प्रबंधन करता है। बेशक नियंत्रक के पास अब अपना 'TryClose' और' CanClose' विधियां (इंटरफेस से) हैं जिन्हें मैं चेक करने के लिए उपयोग करता हूं – MoonKnight

1

सबसे पहले मुझे कहना होगा कि मैं WinForms के साथ काम नहीं करता, लेकिन मुझे लगता है कि आपके कार्यान्वयन में कुछ समस्याएं हैं। सबसे पहले, यह ब्लॉक

private IDocumentController GetDocumentController(string path) 
{ 
    return return CompositionRoot.ResolveAll<IDocumentController>() 
     .SingleOrDefault(provider => provider.Handles(path)); 
} 

इंगित करता है कि आपके कंटेनर में पंजीकृत कई IDocumentControllers हो सकते हैं। अब ध्यान दें कि SpreadsheetController कन्स्ट्रक्टर में ISpreadsheetView लेता है। इसका अर्थ यह है कि जब इस नियंत्रक को हल किया जाता है, तो स्प्रेडशीट व्यू को हल और बनाया जाता है, और यह दृश्य UI नियंत्रण है जो निर्माण के लिए महंगा हो सकता है। अब कल्पना करें कि आपके पास पंजीकृत 20 आईडी दस्तावेज़ नियंत्रक हैं। जब उपरोक्त कोड (GetDocumentController) निष्पादित किया जाता है, तो वे सभी हल हो जाते हैं और 20 यूआई नियंत्रण बनाए जाते हैं, जिनमें से 1 को तत्काल हटा दिया जाता है।

यह अच्छा नहीं है और इंगित करता है कि आपको उस नियंत्रक के निर्माता में एक दृश्य का उदाहरण लेने की आवश्यकता नहीं है। इसके बजाए, आवश्यकता होने पर उस उदाहरण को बनाने के लिए आपको रास्ता की आवश्यकता है, और इससे हमें फ़ैक्टरी पैटर्न मिल जाता है। ISpreadsheetViewFactory (या यहां तक ​​कि IDocumentViewFactory) बनाएं जो आपके लिए IDocumentViews के उदाहरण बनाएगा। कुछ इस तरह:

interface IDocumentViewFactory { 
    ISpreadsheetView Create(IDocumentController controller); 
} 

और कार्यान्वयन

class DocumentViewFactory : IDocumentViewFactory { 
    public ISpreadsheetView Create(IDocumentController controller) { 
     return new SpreadsheetView(controller); 
    } 
} 

फिर, अपने कंटेनर में इस कारखाने, SpreadsheetView के परिवर्तन निर्माता रजिस्टर, SetContainer विधि हटाने, और SpreadsheetController के निर्माता को बदलने IDocumentViewFactory स्वीकार करने के लिए। फिर, सीधे नियंत्रक कन्स्ट्रक्टर में दृश्य बनाएं, क्योंकि ऊपर देखें - यह कुछ भी के लिए संभावित रूप से कई UI नियंत्रण बनाएगा। इसके बजाए, आलसी पैटर्न का उपयोग करें और आवश्यक होने पर केवल स्प्रेडशीट व्यू को तुरंत चालू करें (फैक्ट्री का उपयोग करके)।

आपके दूसरे प्रश्न के लिए - हाँ, आप अपने कंटेनर को अपने GetDocumentController में सेवा लोकेटर के रूप में उपयोग करते हैं। यदि आप इससे बचना चाहते हैं, तो Multi Injection का उपयोग करें और अपने मुख्य दृश्य के निर्माता में IDocumentController की सरणी डालें।

संबंधित मुद्दे