การออกแบบที่ดีที่สุดสำหรับรูปแบบ Windows ที่จะแบ่งปันฟังก์ชั่นทั่วไป


20

ในอดีตที่ผ่านมาฉันเคยใช้การสืบทอดเพื่อให้สามารถขยายแบบฟอร์ม Windows ในแอปพลิเคชันของฉันได้ ถ้าแบบฟอร์มทั้งหมดของฉันมีตัวควบคุมงานศิลปะและฟังก์ชันการทำงานร่วมกันฉันจะสร้างแบบฟอร์มพื้นฐานที่ใช้ตัวควบคุมและฟังก์ชันการทำงานทั่วไปแล้วอนุญาตให้ตัวควบคุมอื่นสืบทอดจากแบบฟอร์มพื้นฐานนั้น อย่างไรก็ตามฉันพบปัญหาเล็กน้อยเกี่ยวกับการออกแบบ

  1. การควบคุมสามารถอยู่ในภาชนะหนึ่งครั้งเท่านั้นดังนั้นการควบคุมแบบคงที่ใด ๆ ที่คุณมีจะยุ่งยาก ตัวอย่างเช่นสมมติว่าคุณมีรูปแบบพื้นฐานที่เรียกว่า BaseForm ซึ่งมี TreeView ซึ่งคุณได้รับการป้องกันและคงที่เพื่อให้อินสแตนซ์อื่น ๆ (ที่ได้รับ) ของคลาสนี้ทั้งหมดสามารถแก้ไขและแสดง TreeView เดียวกันได้ สิ่งนี้จะไม่ทำงานสำหรับหลาย ๆ คลาสที่สืบทอดมาจาก BaseForm เนื่องจาก TreeView นั้นสามารถอยู่ในหนึ่งคอนเทนเนอร์ในแต่ละครั้งเท่านั้น มันน่าจะเป็นในรูปแบบสุดท้ายที่เริ่มต้น แม้ว่าทุกอินสแตนซ์สามารถแก้ไขการควบคุมได้ แต่จะแสดงในเวลาเดียวเท่านั้น แน่นอนว่ามีการแก้ไข แต่พวกมันน่าเกลียด (นี่ดูเหมือนจะเป็นการออกแบบที่แย่มากสำหรับฉันทำไมภาชนะบรรจุหลาย ๆ อันไม่สามารถเก็บพอยน์เตอร์ไปยังวัตถุเดียวกันได้อย่างไรก็ตามมันเป็นอย่างนั้น)

  2. สถานะระหว่างฟอร์มนั่นคือปุ่มสถานะข้อความป้ายกำกับ ฯลฯ ฉันต้องใช้ตัวแปรโกลบอลสำหรับและรีเซ็ตสถานะบนโหลด

  3. สิ่งนี้ไม่ได้รับการสนับสนุนโดยนักออกแบบของ Visual Studio เป็นอย่างดี

มีการออกแบบที่ดีกว่า แต่ยังคงไว้ซึ่งการบำรุงรักษาที่ใช้ง่ายหรือไม่? หรือรูปแบบการสืบทอดยังคงเป็นวิธีที่ดีที่สุด?

อัปเดต ฉันเปลี่ยนจากการดู MVC เป็น MVP เป็นรูปแบบการสังเกตการณ์เป็นรูปแบบกิจกรรม นี่คือสิ่งที่ฉันกำลังคิดอยู่ในขณะนี้โปรดวิจารณ์:

คลาส BaseForm ของฉันจะมีตัวควบคุมและกิจกรรมที่เชื่อมต่อกับตัวควบคุมเหล่านั้นเท่านั้น เหตุการณ์ทั้งหมดที่ต้องมีตรรกะเพื่อจัดการกับมันจะผ่านไปยังคลาส BaseFormPresenter ทันที คลาสนี้จะจัดการข้อมูลจาก UI ดำเนินการทางตรรกะใด ๆ แล้วอัปเดต BaseFormModel แบบจำลองจะเปิดเผยเหตุการณ์ซึ่งจะเกิดขึ้นเมื่อมีการเปลี่ยนแปลงสถานะไปยังคลาสของผู้นำเสนอซึ่งมันจะสมัครสมาชิก (หรือสังเกต) ไปที่ เมื่อผู้นำเสนอได้รับการแจ้งเตือนเหตุการณ์จะดำเนินการตามตรรกะใด ๆ จากนั้นผู้นำเสนอจะแก้ไขมุมมองตามนั้น

จะมีเพียงหนึ่งในแต่ละคลาสโมเดลในหน่วยความจำ แต่อาจมีหลายอินสแตนซ์ของ BaseForm และดังนั้น BaseFormPresenter สิ่งนี้จะแก้ปัญหาของฉันในการซิงโครไนซ์อินสแตนซ์ของ BaseForm กับโมเดลข้อมูลเดียวกัน

คำถาม:

เลเยอร์ใดที่ควรจัดเก็บสิ่งต่าง ๆ เช่นปุ่มกดสุดท้ายเพื่อให้ฉันสามารถเน้นไว้สำหรับผู้ใช้ (เช่นในเมนู CSS) ระหว่างฟอร์ม

โปรดวิจารณ์การออกแบบนี้ ขอบคุณสำหรับความช่วยเหลือของคุณ!


ฉันไม่เห็นว่าทำไมคุณถูกบังคับให้ใช้ตัวแปรทั่วโลก แต่ถ้าใช่ก็ต้องมีวิธีที่ดีกว่า บางทีโรงงานอาจสร้างส่วนประกอบร่วมกับองค์ประกอบแทนที่จะเป็นมรดก?
Stijn

การออกแบบทั้งหมดของคุณมีข้อบกพร่อง หากคุณเข้าใจในสิ่งที่คุณต้องการแล้ว Visual Studio จะไม่สนับสนุนสิ่งที่คุณต้องการ
Ramhound

1
@ Ramhound มันได้รับการสนับสนุนโดยสตูดิโอภาพเพียงไม่ดี นี่คือวิธีที่ Microsoft บอกให้คุณทำ ฉันเพิ่งพบว่ามันเป็นความเจ็บปวดใน A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspxอย่างไรก็ตามถ้าคุณมีความคิดที่ดีกว่าฉันทุกสายตา
Jonathan Henson

@stijn ฉันคิดว่าฉันสามารถมีวิธีการในแต่ละรูปแบบฐานที่จะโหลดรัฐควบคุมลงในอินสแตนซ์อื่น เช่น LoadStatesToNewInstance (อินสแตนซ์ BaseForm) ฉันสามารถเรียกมันได้ทุกเมื่อที่ต้องการแสดงรูปแบบใหม่ของประเภทฐานนั้น form_I_am_about_to_hide.LoadStatesToNewInstance (นี้);
Jonathan Henson

ฉันไม่ใช่นักพัฒนา. net คุณสามารถขยายจำนวนจุดที่ 1 ได้หรือไม่? ฉันอาจมีวิธีแก้ปัญหา
Imran Omar Bukhsh

คำตอบ:


6
  1. ฉันไม่รู้ว่าทำไมคุณต้องมีการควบคุมแบบคงที่ บางทีคุณอาจรู้บางสิ่งที่ฉันทำไม่ได้ ฉันใช้การสืบทอดทางสายตามากมาย แต่ฉันไม่เคยเห็นตัวควบคุมแบบคงที่มาใช้จำเป็น หากคุณมีตัวควบคุม treeview ทั่วไปให้ทุกอินสแตนซ์ของฟอร์มมีอินสแตนซ์ของตัวควบคุมและใช้อินสแตนซ์เดียวของข้อมูลที่ผูกไว้กับทรีวิว

  2. การควบคุมการแบ่งปันสถานะ (ตรงข้ามกับข้อมูล) ระหว่างฟอร์มยังเป็นข้อกำหนดที่ผิดปกติ คุณแน่ใจหรือไม่ว่า FormB จำเป็นต้องรู้เกี่ยวกับสถานะของปุ่มบน FormA จริง ๆ พิจารณาการออกแบบ MVP หรือ MVC คิดว่าแต่ละรูปแบบเป็น "มุมมอง" โง่ที่ไม่รู้อะไรเลยเกี่ยวกับมุมมองอื่น ๆ หรือแม้แต่แอปพลิเคชันเอง ควบคุมแต่ละมุมมองด้วยผู้นำเสนอ / ตัวควบคุมอัจฉริยะ หากเหมาะสมผู้นำเสนอเพียงคนเดียวสามารถควบคุมหลาย ๆ มุมมองได้ เชื่อมโยงวัตถุสถานะกับแต่ละมุมมอง หากคุณมีบางสถานะที่ต้องแชร์ระหว่างมุมมองให้ผู้นำเสนอเป็นสื่อกลางในการพิจารณา (และพิจารณาฐานข้อมูล - ดูด้านล่าง)

  3. ตกลง Visual Studio จะทำให้คุณปวดหัว เมื่อพิจารณาถึงการสืบทอดฟอร์มหรือการควบคุมผู้ใช้คุณต้องชั่งน้ำหนักผลประโยชน์อย่างรอบคอบกับค่าใช้จ่ายที่อาจเกิดขึ้น (และน่าจะเป็น) ของการต่อสู้ด้วยข้อ จำกัด และข้อ จำกัด ที่น่าหงุดหงิดของนักออกแบบฟอร์ม ฉันขอแนะนำให้รักษารูปแบบการสืบทอดให้น้อยที่สุด - ใช้เฉพาะเมื่อผลตอบแทนสูง โปรดทราบว่าคุณสามารถสร้างรูปแบบ "ฐาน" ทั่วไปและแทนที่อินสแตนซ์ของคลาสย่อยได้เพียงครั้งเดียวสำหรับเด็กแต่ละคนที่ต้องการจะเป็น "ลูก" แล้วปรับแต่งแบบทันที สิ่งนี้สมเหตุสมผลเมื่อความแตกต่างระหว่างแต่ละเวอร์ชันของฟอร์มนั้นน้อยกว่าเมื่อเทียบกับแง่มุมที่แชร์ (IOW: รูปแบบพื้นฐานที่ซับซ้อนรูปแบบเด็กที่ซับซ้อนน้อยกว่าเท่านั้น)

ใช้ประโยชน์จาก usercontrols เมื่อช่วยป้องกันการทำซ้ำการพัฒนา UI ที่สำคัญ พิจารณาการสืบทอดของผู้ใช้งาน แต่ใช้สิ่งที่ต้องพิจารณาเช่นเดียวกับการสืบทอดแบบฟอร์ม

ฉันคิดว่าคำแนะนำที่สำคัญที่สุดที่ฉันสามารถเสนอคือถ้าคุณไม่ได้ใช้รูปแบบมุมมอง / ตัวควบคุมบางรูปแบบในปัจจุบันฉันขอแนะนำให้คุณเริ่มทำเช่นนั้น มันบังคับให้คุณเรียนรู้และซาบซึ้งในประโยชน์ของการแยกและการแยกชั้น

ตอบสนองต่อการปรับปรุงของคุณ

เลเยอร์ใดที่ควรจัดเก็บสิ่งต่าง ๆ ปุ่มกดสุดท้ายเพื่อให้ฉันสามารถเน้นให้กับผู้ใช้ ...

คุณสามารถแชร์สถานะระหว่างมุมมองได้เหมือนกับที่คุณแชร์สถานะระหว่างผู้นำเสนอและมุมมอง สร้างคลาสพิเศษ SharedViewState เพื่อความเรียบง่ายคุณสามารถทำให้มันเป็นซิงเกิลตันหรือคุณสามารถอินสแตนซ์มันในผู้นำเสนอหลักและส่งต่อไปยังทุกมุมมอง (ผ่านผู้นำเสนอ) จากที่นั่น เมื่อสถานะนั้นเกี่ยวข้องกับตัวควบคุมให้ใช้การเชื่อมข้อมูลถ้าเป็นไปได้ มากที่สุดControlคุณสมบัติสามารถผูกข้อมูล ตัวอย่างเช่นคุณสมบัติ BackColor ของปุ่มอาจถูกผูกไว้กับคุณสมบัติของคลาส SharedViewState ของคุณ หากคุณทำสิ่งนี้ผูกพันกับฟอร์มทั้งหมดที่มีปุ่มเหมือนกันคุณสามารถไฮไลต์ Button1 ในทุกรูปแบบเพียงแค่ตั้งค่าSharedViewState.Button1BackColor = someColorในทุกรูปแบบโดยการตั้งค่า

หากคุณไม่คุ้นเคยกับฐานข้อมูล WinForms ให้เข้าสู่ MSDN และอ่านหนังสือ มันไม่ยาก เรียนรู้เกี่ยวกับINotifyPropertyChangedคุณครึ่งทางแล้ว

นี่คือการใช้งานทั่วไปของคลาส viewstate ด้วยคุณสมบัติ Button1BackColor เป็นตัวอย่าง:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}

ขอบคุณสำหรับคำตอบที่รอบคอบ คุณรู้จักการอ้างอิงที่ดีกับรูปแบบมุมมอง / ตัวควบคุมหรือไม่?
Jonathan Henson

1
ฉันจำได้ว่าคิดว่านี่เป็นคำนำที่ค่อนข้างดี: codebetter.com/jeremymiller/2007/05/22/ …
Igby Largeman

โปรดดูการปรับปรุงของฉัน
Jonathan Henson

@JonathanHenson: คำตอบอัพเดทแล้ว
Igby Largeman

5

ฉันบำรุงรักษาแอปพลิเคชัน WinForms / WPF ใหม่ตามรูปแบบ MVVM และการควบคุมการอัปเดตการปรับปรุงการควบคุมมันเริ่มต้นเป็น WinForms จากนั้นฉันสร้างรุ่น WPF เนื่องจากความสำคัญทางการตลาดของ UI ที่ดูดี ฉันคิดว่ามันจะเป็นข้อ จำกัด การออกแบบที่น่าสนใจในการรักษาแอพพลิเคชั่นที่รองรับเทคโนโลยี UI ที่แตกต่างกันสองอย่างด้วยรหัสแบ็กเอนด์เดียวกัน (viewmodels และรุ่น) และฉันต้องบอกว่าฉันพอใจกับวิธีนี้มาก

ในแอพของฉันเมื่อใดก็ตามที่ฉันต้องการแบ่งปันฟังก์ชั่นการทำงานระหว่างส่วนต่าง ๆ ของ UI ฉันใช้การควบคุมแบบกำหนดเองหรือการควบคุมผู้ใช้ (คลาสที่ได้รับมาจาก WPF UserControl หรือ WinForms UserControl) ในรุ่น WinForms จะมี UserControl อยู่ใน UserControl อื่นภายในคลาสที่ได้รับของ TabPage ซึ่งในที่สุดก็อยู่ในการควบคุมแท็บในแบบฟอร์มหลัก เวอร์ชัน WPF มี UserControls ซ้อนกันลึกลงหนึ่งระดับ ด้วยการควบคุมที่กำหนดเองคุณสามารถสร้าง UI ใหม่จาก UserControls ที่คุณสร้างไว้ก่อนหน้านี้ได้อย่างง่ายดาย

เนื่องจากฉันใช้รูปแบบ MVVM ฉันจึงใส่ตรรกะโปรแกรมให้มากที่สุดเท่าที่จะเป็นไปได้ใน ViewModel (รวมถึงการนำเสนอ / โมเดลการนำทาง ) หรือ Model (ขึ้นอยู่กับว่ารหัสเกี่ยวข้องกับส่วนติดต่อผู้ใช้หรือไม่) แต่เนื่องจาก ViewModel เดียวกัน ถูกใช้โดยทั้งมุมมอง WinForms และมุมมอง WPF, ViewModel ไม่ได้รับอนุญาตให้มีรหัสที่ออกแบบมาสำหรับ WinForms หรือ WPF หรือโต้ตอบโดยตรงกับ UI; รหัสดังกล่าวจะต้องเข้าสู่มุมมอง

นอกจากนี้ในรูปแบบ MVVM วัตถุ UI ควรหลีกเลี่ยงการโต้ตอบซึ่งกันและกัน! แต่พวกเขามีปฏิสัมพันธ์กับ viewmodels ตัวอย่างเช่นกล่องข้อความจะไม่ถามกล่องรายการที่อยู่ใกล้เคียงซึ่งรายการที่เลือก; แต่กล่องรายการจะบันทึกการอ้างอิงไปยังรายการที่เลือกในปัจจุบันที่ใดที่หนึ่งในเลเยอร์ viewmodel และ TextBox จะสืบค้นเลเยอร์ viewmodel เพื่อค้นหาสิ่งที่เลือกไว้ในขณะนี้ วิธีนี้จะช่วยแก้ปัญหาของคุณเกี่ยวกับการค้นหาว่ามีการกดปุ่มใดในรูปแบบหนึ่งจากภายในรูปแบบอื่น คุณเพียงแค่แชร์วัตถุโมเดลการนำทาง (ซึ่งเป็นส่วนหนึ่งของเลเยอร์มุมมองโมเดลของแอปพลิเคชัน) ระหว่างสองแบบฟอร์มและวางคุณสมบัติในวัตถุนั้นที่แสดงถึงปุ่มที่ถูกผลัก

WinForms เองไม่สนับสนุนรูปแบบ MVVM ได้เป็นอย่างดี แต่การควบคุมการอัปเดตให้วิธีการเฉพาะของตัวเอง MVVM เป็นห้องสมุดที่อยู่ด้านบนของ WinForms

ในความคิดของฉันวิธีการออกแบบโปรแกรมนี้ทำงานได้ดีมากและฉันวางแผนที่จะใช้กับโครงการในอนาคต เหตุผลที่ใช้งานได้ดีคือ (1) ที่ Update Controls จัดการการพึ่งพาโดยอัตโนมัติและ (2) ว่ามันชัดเจนมากว่าคุณควรจัดโครงสร้างรหัสของคุณอย่างไร: รหัสทั้งหมดที่โต้ตอบกับวัตถุ UI นั้นอยู่ในมุมมองโค้ดทั้งหมดที่เป็น เกี่ยวข้องกับ UI แต่ไม่ต้องโต้ตอบกับวัตถุ UI อยู่ใน ViewModel บ่อยครั้งที่คุณจะแบ่งรหัสของคุณออกเป็นสองส่วนส่วนหนึ่งสำหรับมุมมองและอีกส่วนหนึ่งสำหรับ ViewModel ฉันมีปัญหาในการออกแบบเมนูบริบทในระบบของฉัน แต่ในที่สุดฉันก็มาพร้อมกับการออกแบบสำหรับสิ่งนั้นด้วย

ฉันยกย่องชมเชยการควบคุมการอัปเดตในบล็อกของฉันด้วย วิธีการนี้ใช้เวลาเป็นอย่างมากในการทำความคุ้นเคยและในแอพขนาดใหญ่ (เช่นถ้ากล่องรายการของคุณมีหลายพันรายการ) คุณอาจประสบปัญหาประสิทธิภาพเนื่องจากข้อ จำกัด ในปัจจุบันของการจัดการการพึ่งพาอัตโนมัติ


3

ฉันจะตอบคำถามนี้แม้ว่าคุณจะตอบรับแล้ว

อย่างที่คนอื่น ๆ ชี้ฉันไม่เข้าใจว่าทำไมคุณต้องใช้อะไรคงที่ ดูเหมือนว่าคุณกำลังทำอะไรผิดไปมาก

อย่างไรก็ตามฉันมีปัญหาเช่นเดียวกับคุณ: ในแอปพลิเคชัน WinForms ของฉันฉันมีหลายรูปแบบที่ใช้งานได้ร่วมกันและมีการควบคุมบางอย่าง นอกจากนี้ฟอร์มทั้งหมดของฉันได้รับมาจากฟอร์มพื้นฐานแล้ว (เรียกว่า "MyForm") ซึ่งเพิ่มฟังก์ชันระดับกรอบ (โดยไม่คำนึงถึงแอปพลิเคชัน) ตัวออกแบบฟอร์มของ Visual Studio สนับสนุนรูปแบบการแก้ไขที่สืบทอดมาจากฟอร์มอื่น ๆ ฝึกใช้งานได้ตราบใดที่แบบฟอร์มของคุณไม่ทำอะไรมากไปกว่า "Hello, world! - OK - Cancel"

สิ่งที่ฉันทำคือ: ฉันเก็บคลาสพื้นฐานสามัญของฉัน "MyForm" ซึ่งค่อนข้างซับซ้อนและฉันได้รับแบบฟอร์มใบสมัครทั้งหมดของฉันต่อไป อย่างไรก็ตามฉันไม่ได้ทำอะไรเลยในแบบฟอร์มเหล่านี้ดังนั้น VS Forms Designer จึงไม่มีปัญหาในการแก้ไข ฟอร์มเหล่านี้ประกอบด้วยรหัสที่ผู้ออกแบบฟอร์มสร้างขึ้นสำหรับพวกเขาเท่านั้น จากนั้นฉันก็มีลำดับชั้นแบบขนานแยกกันของวัตถุที่ฉันเรียกว่า "ตัวแทน" ซึ่งมีฟังก์ชั่นเฉพาะแอปพลิเคชันทั้งหมดเช่นการเริ่มต้นการควบคุมการจัดการเหตุการณ์ที่สร้างโดยฟอร์มและการควบคุม ฯลฯ มีแบบหนึ่งต่อหนึ่ง การโต้ตอบระหว่างคลาสตัวแทนและไดอะล็อกในแอปพลิเคชันของฉัน: มีคลาสตัวแทนตัวแทนที่ตรงกับ "MyForm" จากนั้นคลาสตัวแทนตัวแทนอื่นจะได้รับซึ่งสอดคล้องกับ "MyApplicationForm"

ตัวแทนเสมือนแต่ละคนยอมรับว่าเป็นรูปแบบเฉพาะของเวลาการก่อสร้างและจะแนบกับตัวมันเองโดยการลงทะเบียนสำหรับเหตุการณ์ นอกจากนี้ยังมอบหมายให้ฐานตลอดจน "MySurrogate" ซึ่งยอมรับ "MyForm" ตัวแทนลงทะเบียนกับเหตุการณ์ "จำหน่าย" ของแบบฟอร์มดังนั้นเมื่อแบบฟอร์มถูกทำลายตัวแทนจะเรียกใช้ overridable ในตัวเองเพื่อให้มันและลูกหลานทั้งหมดสามารถทำการล้างข้อมูลได้ (ยกเลิกการลงทะเบียนจากเหตุการณ์ ฯลฯ )

มันใช้งานได้ดีจนถึงตอนนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.