Desain terbaik untuk formulir Windows yang akan berbagi fungsionalitas umum


20

Di masa lalu, saya telah menggunakan warisan untuk memungkinkan ekstensi formulir Windows di aplikasi saya. Jika semua formulir saya akan memiliki kontrol umum, karya seni, dan fungsionalitas, saya akan membuat formulir dasar yang menerapkan kontrol dan fungsionalitas umum dan kemudian mengizinkan kontrol lain untuk mewarisi dari formulir dasar itu. Namun, saya mengalami beberapa masalah dengan desain itu.

  1. Kontrol hanya dapat berada dalam satu wadah pada satu waktu, sehingga kontrol statis yang Anda miliki akan menjadi rumit. Sebagai contoh: Misalkan Anda memiliki bentuk dasar yang disebut BaseForm yang berisi TreeView yang Anda buat terproteksi dan statis sehingga semua instance (turunan) lainnya dari kelas ini dapat memodifikasi dan menampilkan TreeView yang sama. Ini tidak akan berfungsi untuk beberapa kelas yang diwarisi dari BaseForm, karena TreeView itu hanya bisa dalam satu wadah pada satu waktu. Kemungkinan akan berada pada bentuk terakhir diinisialisasi. Meskipun setiap instance dapat mengedit kontrol, itu hanya akan ditampilkan dalam satu pada waktu tertentu. Tentu saja, ada jalan keluar, tetapi semuanya jelek. (Ini sepertinya desain yang benar-benar buruk bagiku. Mengapa beberapa kontainer tidak dapat menyimpan pointer ke objek yang sama? Bagaimanapun, itu adalah apa adanya.)

  2. Sebutkan di antara formulir, yaitu, status tombol, teks label, dll., Saya harus menggunakan variabel global untuk dan mengatur ulang status pada Load.

  3. Ini tidak benar-benar didukung oleh desainer Visual Studio dengan sangat baik.

Apakah ada desain yang lebih baik, namun masih mudah dirawat untuk digunakan? Atau apakah pewarisan bentuk masih merupakan pendekatan terbaik?

Pembaruan Saya beralih dari melihat MVC ke MVP ke Pola Pengamat ke Pola Peristiwa. Inilah yang saya pikirkan untuk saat ini, mohon kritik:

Kelas BaseForm saya hanya akan berisi kontrol, dan acara yang terhubung ke kontrol itu. Semua peristiwa yang memerlukan segala jenis logika untuk menanganinya akan segera diteruskan ke kelas BaseFormPresenter. Kelas ini akan menangani data dari UI, melakukan operasi logis apa pun, dan kemudian memperbarui BaseFormModel. Model akan memaparkan acara, yang akan diluncurkan setelah perubahan status, ke kelas Presenter, di mana ia akan berlangganan (atau mengamati). Ketika Presenter menerima pemberitahuan acara, itu akan melakukan logika apa pun, dan kemudian Presenter akan memodifikasi tampilan yang sesuai.

Hanya akan ada satu dari setiap kelas Model dalam memori, tetapi berpotensi ada banyak instance dari BaseForm dan karenanya BaseFormPresenter. Ini akan memecahkan masalah saya menyinkronkan setiap instance dari BaseForm ke model data yang sama.

Pertanyaan:

Lapisan mana yang harus menyimpan hal-hal seperti, tombol terakhir yang ditekan, sehingga saya dapat tetap disorot untuk pengguna (seperti dalam menu CSS) di antara formulir?

Harap kritik desain ini. Terima kasih atas bantuan Anda!


Saya tidak mengerti mengapa Anda dipaksa untuk menggunakan variabel global, tetapi jika demikian maka ya pasti ada pendekatan yang lebih baik. Mungkin pabrik untuk membuat komponen umum dikombinasikan dengan komposisi, bukan warisan?
stijn

Seluruh desain Anda cacat. Jika Anda sudah mengerti apa yang ingin Anda lakukan tidak didukung oleh Visual Studio yang seharusnya memberi tahu Anda sesuatu.
Ramhound

1
@Ramhound Didukung oleh studio visual, hanya saja tidak baik. Beginilah cara Microsoft memberi tahu Anda untuk melakukannya. Saya hanya merasa sakit di A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspx Bagaimanapun, jika Anda memiliki ide yang lebih baik, saya semua mata.
Jonathan Henson

@stijn Saya kira saya bisa memiliki metode di setiap bentuk dasar yang akan memuat status kontrol ke instance lain. yaitu LoadStatesToNewInstance (instance BaseForm). Saya bisa menelepon kapan saja saya ingin menunjukkan bentuk baru dari tipe dasar itu. form_I_am_about_to_hide.LoadStatesToNewInstance (ini);
Jonathan Henson

Saya bukan pengembang .net, dapatkah Anda memperluas pada poin nomor 1? Saya mungkin punya solusi
Imran Omar Bukhsh

Jawaban:


6
  1. Saya tidak tahu mengapa Anda memerlukan kontrol statis. Mungkin Anda tahu sesuatu yang tidak saya ketahui. Saya telah menggunakan banyak warisan visual tetapi saya belum pernah melihat kontrol statis diperlukan. Jika Anda memiliki kontrol treeview yang umum, biarkan setiap instance form memiliki instance kontrolnya sendiri, dan bagikan satu instance tunggal dari data yang terikat ke tampilan tree.

  2. Berbagi status kontrol (bukan data) antara formulir juga merupakan persyaratan yang tidak biasa. Apakah Anda yakin FormB benar-benar perlu tahu tentang keadaan tombol pada FormA? Pertimbangkan desain MVP atau MVC. Pikirkan setiap bentuk sebagai "tampilan" bodoh yang tidak tahu apa-apa tentang tampilan lain atau bahkan aplikasi itu sendiri. Awasi setiap tampilan dengan presenter / pengontrol cerdas. Jika masuk akal, seorang presenter tunggal dapat mengawasi beberapa pandangan. Kaitkan objek keadaan dengan setiap tampilan. Jika Anda memiliki beberapa kondisi yang perlu dibagikan di antara tampilan, biarkan presenter memediasi ini (dan pertimbangkan penyatuan data - lihat di bawah).

  3. Setuju, Visual Studio akan membuat Anda sakit kepala. Saat mempertimbangkan warisan form atau kontrol pengguna, Anda perlu mempertimbangkan manfaat dengan hati-hati terhadap potensi biaya (dan kemungkinan) pergulatan dengan keanehan dan keterbatasan frustasi dari formulir. Saya sarankan menjaga warisan bentuk ke minimum - menggunakannya hanya ketika hasilnya tinggi. Ingatlah bahwa, sebagai alternatif untuk subklasifikasi, Anda dapat membuat bentuk "dasar" yang umum dan cukup instantiate satu kali untuk setiap "anak" yang akan datang dan kemudian sesuaikan sendiri. Ini masuk akal ketika perbedaan antara setiap versi formulir kecil dibandingkan dengan aspek yang dibagikan. (TKI: bentuk dasar yang kompleks, bentuk anak hanya-sedikit-lebih-kompleks)

Manfaatkan kontrol pengguna saat ini membantu Anda mencegah duplikasi signifikan pengembangan UI. Pertimbangkan pewarisan kontrol pengguna tetapi terapkan pertimbangan yang sama dengan pewarisan bentuk.

Saya pikir saran paling penting yang dapat saya tawarkan adalah, jika Anda saat ini tidak menggunakan beberapa bentuk pola view / controller, saya sangat menyarankan Anda untuk mulai melakukannya. Ini memaksa Anda untuk belajar dan menghargai manfaat lepas-kupas dan pemisahan lapisan.

menanggapi pembaruan Anda

Lapisan mana yang harus menyimpan hal-hal seperti, tombol terakhir yang ditekan, sehingga saya dapat tetap disorot untuk pengguna ...

Anda dapat berbagi keadaan di antara tampilan seperti halnya Anda akan berbagi keadaan antara presenter dan pandangannya. Buat kelas khusus SharedViewState. Untuk kesederhanaan Anda dapat menjadikannya sebagai singleton, atau Anda dapat instantiate di presenter utama dan meneruskannya ke semua tampilan (melalui presenter mereka) dari sana. Saat status dikaitkan dengan kontrol, gunakan pengikatan data jika memungkinkan. Sebagian besar Controlproperti dapat terikat data. Misalnya properti BackColor dari Button dapat diikat ke properti kelas SharedViewState Anda. Jika Anda melakukan ini mengikat semua formulir yang memiliki tombol identik, Anda dapat menyorot Button1 pada semua formulir hanya dengan mengatur SharedViewState.Button1BackColor = someColor.

Jika Anda tidak terbiasa dengan penyatuan data WinForms, tekan MSDN dan lakukan beberapa bacaan. Itu tidak sulit. Pelajari tentang INotifyPropertyChangeddan Anda setengah jalan di sana.

Berikut ini adalah implementasi khas dari kelas tampilan dengan properti Button1BackColor sebagai contoh:

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

Terima kasih atas jawaban bijaksana Anda. Apakah Anda tahu referensi yang baik untuk pola tampilan / pengontrol?
Jonathan Henson

1
Saya ingat berpikir ini intro yang cukup bagus: codebetter.com/jeremymiller/2007/05/22/…
Igby Largeman

silakan lihat pembaruan saya.
Jonathan Henson

@ JonathanHenson: jawaban diperbarui.
Igby Largeman

5

Saya memelihara aplikasi WinForms / WPF baru berdasarkan pola MVVM dan Pembaruan Kontrol . Dimulai sebagai WinForms, kemudian saya membuat versi WPF karena pentingnya pemasaran dari UI yang terlihat bagus. Saya pikir itu akan menjadi kendala desain yang menarik untuk memelihara aplikasi yang mendukung dua teknologi UI yang sama sekali berbeda dengan kode backend yang sama (model dan model viewend), dan saya harus mengatakan saya cukup puas dengan pendekatan ini.

Dalam aplikasi saya, setiap kali saya perlu berbagi fungsionalitas antara bagian-bagian UI, saya menggunakan kontrol khusus atau kontrol pengguna (kelas yang berasal dari WPF UserControl atau WinForms UserControl). Dalam versi WinForms ada UserControl di dalam UserControl lain di dalam kelas turunan dari TabPage, yang akhirnya di dalam kontrol tab pada formulir utama. Versi WPF sebenarnya memiliki UserControls bersarang satu tingkat lebih dalam. Menggunakan kontrol khusus, Anda dapat dengan mudah membuat UI baru dari UserControls yang Anda buat sebelumnya.

Karena saya menggunakan pola MVVM, saya memasukkan logika program sebanyak mungkin ke dalam ViewModel (termasuk Model Presentasi / Navigasi ) atau Model (tergantung pada apakah kode tersebut terkait dengan antarmuka pengguna atau tidak), tetapi karena model ViewModel yang sama digunakan oleh WinForms view dan WPF view, ViewModel tidak diizinkan berisi kode yang dirancang untuk WinForms atau WPF atau yang secara langsung berinteraksi dengan UI; kode tersebut HARUS masuk ke tampilan.

Juga dalam pola MVVM, objek UI harus menghindari interaksi satu sama lain! Sebaliknya mereka berinteraksi dengan model viewmodels. Misalnya TextBox tidak akan meminta ListBox terdekat item mana yang dipilih; sebagai gantinya kotak daftar akan menyimpan referensi ke item yang dipilih saat ini di suatu tempat di lapisan model view, dan TextBox akan meminta layer viewmodel untuk mencari tahu apa yang dipilih sekarang. Ini menyelesaikan masalah Anda tentang mencari tahu tombol mana yang ditekan dalam satu bentuk dari dalam bentuk yang berbeda. Anda hanya berbagi objek Model Navigasi (yang merupakan bagian dari lapisan model tampilan aplikasi) antara dua bentuk dan meletakkan properti di objek yang mewakili tombol mana yang ditekan.

WinForms sendiri tidak mendukung pola MVVM dengan baik, tetapi Update Controls menyediakan pendekatan uniknya sendiri MVVM sebagai pustaka yang berada di atas WinForms.

Menurut pendapat saya, pendekatan desain program ini bekerja dengan sangat baik dan saya berencana untuk menggunakannya pada proyek mendatang. Alasan kerjanya sangat baik adalah (1) Pembaruan Kontrol mengelola dependensi secara otomatis, dan (2) bahwa biasanya sangat jelas bagaimana Anda harus menyusun kode Anda: semua kode yang berinteraksi dengan objek UI termasuk dalam View, semua kode yang Terkait UI tetapi tidak HARUS berinteraksi dengan objek UI milik dalam ViewModel. Cukup sering Anda akan membagi kode Anda menjadi dua bagian, satu bagian untuk View dan bagian lain untuk ViewModel. Saya mengalami beberapa kesulitan merancang menu konteks di sistem saya, tetapi akhirnya saya datang dengan desain untuk itu juga.

Saya juga mengoceh tentang Kontrol Pembaruan di blog saya . Pendekatan ini memang membutuhkan banyak pembiasaan, dan dalam aplikasi skala besar (misalnya jika kotak daftar Anda berisi ribuan item), Anda mungkin mengalami masalah kinerja karena keterbatasan manajemen ketergantungan otomatis saat ini.


3

Saya akan menjawab ini meskipun Anda sudah menerima jawaban.

Seperti yang telah ditunjukkan orang lain, saya tidak mengerti mengapa Anda harus menggunakan sesuatu yang statis; ini terdengar seperti Anda telah melakukan sesuatu yang sangat salah.

Lagi pula, saya memiliki masalah yang sama dengan Anda: dalam aplikasi WinForms saya, saya memiliki beberapa bentuk yang berbagi beberapa fungsi dan beberapa kontrol. Juga, semua formulir saya sudah berasal dari formulir dasar (sebut saja "MyForm") yang menambahkan fungsionalitas tingkat kerangka kerja (terlepas dari aplikasi.) Perancang Formulir Visual Studio seharusnya mendukung formulir pengeditan yang mewarisi dari formulir lain, tetapi dalam berlatih itu hanya berfungsi selama formulir Anda tidak lebih dari "Halo, dunia! - OK - Batalkan".

Apa yang akhirnya saya lakukan adalah ini: Saya menjaga kelas umum saya "MyForm", yang cukup kompleks, dan saya terus mengambil semua formulir aplikasi dari sana. Namun, saya sama sekali tidak melakukan apa pun dalam formulir ini, sehingga Desainer Formulir VS tidak memiliki masalah dalam mengeditnya. Formulir ini hanya terdiri dari kode yang dihasilkan oleh Perancang Formulir untuk mereka. Kemudian, saya memiliki hierarki paralel paralel objek yang saya sebut "Pengganti" yang berisi semua fungsi khusus aplikasi, seperti menginisialisasi kontrol, menangani peristiwa yang dihasilkan oleh formulir dan kontrol, dll. Ada satu-ke-satu korespondensi antara kelas pengganti dan dialog dalam aplikasi saya: ada kelas pengganti dasar yang sesuai dengan "MyForm", maka kelas pengganti lainnya diturunkan yang sesuai dengan "MyApplicationForm",

Setiap pengganti menerima sebagai parameter waktu konstruksi jenis tertentu dari bentuk, dan itu melekat dengan mendaftar untuk acara tersebut. Ini juga mendelegasikan ke pangkalan, sampai ke "MySurrogate" yang menerima "MyForm". Pengganti itu mendaftar dengan acara "Disingkirkan" dari formulir, sehingga ketika formulir dihancurkan, pengganti meminta pada dirinya sendiri sehingga sehingga, dan semua turunannya, dapat melakukan pembersihan. (Deregister dari acara, dll.)

Sejauh ini sudah berfungsi dengan baik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.