Saya sedang mengerjakan aplikasi WPF dengan tampilan yang membutuhkan banyak konversi nilai. Awalnya, filosofi saya (sebagian terinspirasi oleh debat langsung tentang XAML Disciples ) adalah bahwa saya harus membuat model tampilan ketat tentang mendukung persyaratan data tampilan. Ini berarti bahwa konversi nilai apa pun yang diperlukan untuk mengubah data menjadi hal-hal seperti visibilitas, kuas, ukuran, dll. Akan ditangani dengan pengonversi nilai dan pengonversi multi-nilai. Secara konseptual, ini tampak cukup elegan. Model tampilan dan tampilan keduanya memiliki tujuan yang berbeda dan dipisahkan dengan baik. Garis yang jelas akan ditarik antara "data" dan "lihat".
Nah, setelah memberikan strategi ini "percobaan lama di kampus", saya ragu apakah saya ingin terus mengembangkan cara ini. Saya sebenarnya sangat mempertimbangkan membuang konverter nilai dan menempatkan tanggung jawab untuk (hampir) semua konversi nilai tepat di tangan model tampilan.
Kenyataan menggunakan konverter nilai sepertinya tidak sesuai dengan nilai yang tampak dari kekhawatiran yang dipisahkan secara bersih. Masalah terbesar saya dengan konverter nilai adalah mereka membosankan untuk digunakan. Anda harus membuat kelas baru, menerapkan IValueConverter
atau IMultiValueConverter
, memberikan nilai atau nilai dari object
jenis yang benar, menguji DependencyProperty.Unset
(setidaknya untuk konverter multi-nilai), menulis logika konversi, mendaftarkan konverter dalam kamus sumber daya [lihat pembaruan di bawah ini ], dan akhirnya, hubungkan konverter menggunakan XAML yang agak verbose (yang membutuhkan penggunaan string ajaib untuk pengikatan dan nama konverter[lihat pembaruan di bawah]). Proses debug juga bukan piknik, karena pesan kesalahan seringkali samar, terutama dalam mode desain Visual Studio / Expression Blend.
Ini bukan untuk mengatakan bahwa alternatif - membuat model tampilan bertanggung jawab untuk semua konversi nilai - merupakan peningkatan. Ini bisa jadi masalah rumput yang lebih hijau di sisi lain. Selain kehilangan pemisahan kekhawatiran yang elegan, Anda harus menulis banyak properti turunan dan memastikan Anda menelepon dengan teliti RaisePropertyChanged(() => DerivedProperty)
saat menetapkan properti dasar, yang bisa terbukti menjadi masalah pemeliharaan yang tidak menyenangkan.
Berikut ini adalah daftar awal yang saya kumpulkan tentang pro dan kontra dari mengizinkan model tampilan untuk menangani logika konversi dan menghilangkan konverter nilai:
- Pro:
- Jumlah binding yang lebih sedikit karena multi-konverter dihilangkan
- Lebih sedikit string ajaib (jalur mengikat
+ nama sumber daya konverter) Tidak ada lagi mendaftar setiap konverter (ditambah mempertahankan daftar ini)- Lebih sedikit pekerjaan untuk menulis setiap konverter (tidak perlu antarmuka implementasi atau casting)
- Dapat dengan mudah menyuntikkan dependensi untuk membantu dengan konversi (misalnya, tabel warna)
- Markup XAML kurang verbose dan lebih mudah dibaca
- Penggunaan kembali konverter masih dimungkinkan (walaupun beberapa perencanaan diperlukan)
- Tidak ada masalah misterius dengan DependencyProperty.Unset (masalah yang saya perhatikan dengan konverter multi-nilai)
* Dicoret menunjukkan manfaat yang hilang jika Anda menggunakan ekstensi markup (lihat pembaruan di bawah)
- Cons:
- Kopling yang lebih kuat antara model tampilan dan tampilan (misalnya, properti harus berurusan dengan konsep seperti visibilitas dan kuas)
- Lebih banyak properti total untuk memungkinkan pemetaan langsung untuk setiap ikatan yang terlihat
(lihat Pembaruan 2 di bawah)RaisePropertyChanged
harus dipanggil untuk setiap properti turunan- Harus tetap mengandalkan konverter jika konversi didasarkan pada properti elemen UI
Jadi, seperti yang mungkin bisa Anda katakan, saya memiliki beberapa mulas tentang masalah ini. Saya sangat ragu untuk turun ke jalan refactoring hanya untuk menyadari bahwa proses pengkodean sama tidak efisien dan membosankan apakah saya menggunakan konverter nilai atau mengekspos berbagai properti konversi nilai dalam model tampilan saya.
Apakah saya kehilangan pro / kontra? Bagi mereka yang telah mencoba kedua cara konversi nilai, yang menurut Anda bekerja lebih baik untuk Anda dan mengapa? Apakah ada alternatif lain? (Para murid menyebutkan sesuatu tentang penyedia deskriptor tipe, tetapi saya tidak bisa memahami apa yang mereka bicarakan. Setiap wawasan tentang ini akan dihargai.)
Memperbarui
Saya mengetahui hari ini bahwa mungkin untuk menggunakan sesuatu yang disebut "ekstensi markup" untuk menghilangkan keharusan mendaftarkan konverter nilai. Bahkan, itu tidak hanya menghilangkan kebutuhan untuk mendaftarkan mereka, tetapi sebenarnya memberikan intellisense untuk memilih konverter saat Anda mengetik Converter=
. Inilah artikel yang membuat saya mulai: http://www.wpftutorial.net/ValueConverters.html .
Kemampuan untuk menggunakan ekstensi markup mengubah keseimbangan dalam daftar pro dan kontra dan diskusi saya di atas (lihat dicoret).
Sebagai hasil dari wahyu ini, saya bereksperimen dengan sistem hibrida tempat saya menggunakan konverter untuk BoolToVisibility
dan apa yang saya sebut MatchToVisibility
dan model tampilan untuk semua konversi lainnya. MatchToVisibility pada dasarnya adalah konverter yang memungkinkan saya memeriksa apakah nilai terikat (biasanya enum) cocok dengan satu atau lebih nilai yang ditentukan dalam XAML.
Contoh:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
Pada dasarnya yang dilakukan adalah memeriksa apakah statusnya Selesai atau Dibatalkan. Jika ya, maka visibilitas mendapat set ke "Visible". Kalau tidak, itu akan ditetapkan ke "Tersembunyi". Ini ternyata merupakan skenario yang sangat umum, dan memiliki konverter ini menyelamatkan saya sekitar 15 properti pada model tampilan saya (ditambah pernyataan RaisePropertyChanged terkait). Perhatikan bahwa ketika Anda mengetik Converter={vc:
, "MatchToVisibility" muncul di menu intellisense. Hal ini secara nyata mengurangi kemungkinan kesalahan dan menjadikan penggunaan konverter nilai menjadi tidak terlalu membosankan (Anda tidak harus mengingat atau mencari nama konverter nilai yang Anda inginkan).
Jika Anda penasaran, saya akan menempelkan kode di bawah ini. Salah satu fitur penting dari pelaksanaan ini MatchToVisibility
adalah bahwa hal itu memeriksa untuk melihat apakah nilai terikat adalah enum
, dan jika itu, itu cek untuk memastikan Value1
, Value2
, dll juga enums dari jenis yang sama. Ini memberikan waktu desain dan run-time memeriksa apakah ada nilai enum yang salah ketik. Untuk meningkatkan ini menjadi pemeriksaan waktu kompilasi, Anda dapat menggunakan yang berikut sebagai gantinya (saya mengetik ini dengan tangan jadi tolong maafkan saya jika saya melakukan kesalahan):
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
Meskipun ini lebih aman, terlalu bertele-tele untuk menjadi layak untuk saya. Saya mungkin juga hanya menggunakan properti pada model tampilan jika saya akan melakukan ini. Lagi pula, saya menemukan bahwa pemeriksaan desain-waktu sangat memadai untuk skenario yang saya coba sejauh ini.
Ini kode untuk MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
Ini kode untuk BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Inilah metode ekstensi ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
Perbarui 2
Sejak saya memposting pertanyaan ini, saya telah menemukan proyek open-source yang menggunakan "Tenun IL" untuk menyuntikkan kode NotifyPropertyChanged untuk properti dan properti dependen. Hal ini menjadikan penerapan visi Josh Smith tentang model tampilan sebagai "konverter nilai steroid" menjadi sangat mudah. Anda cukup menggunakan "Properti yang Diimplementasikan Otomatis", dan penenun akan melakukan sisanya.
Contoh:
Jika saya memasukkan kode ini:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... inilah yang akan dikompilasi:
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
Itu adalah penghematan besar dalam jumlah kode yang harus Anda ketik, baca, gulirkan masa lalu, dll. Yang lebih penting, meskipun, itu menyelamatkan Anda dari keharusan untuk mencari tahu apa dependensi Anda. Anda dapat menambahkan seperti "properti dapatkan" FullName
tanpa harus bersusah payah menaiki rantai ketergantungan untuk menambahkan RaisePropertyChanged()
panggilan.
Apa nama proyek sumber terbuka ini? Versi aslinya disebut "NotifyPropertyWeaver", tetapi pemiliknya (Simon Potter) sejak itu menciptakan platform yang disebut "Fody" untuk menampung seluruh rangkaian penenun IL. Setara dengan NotifyPropertyWeaver di bawah platform baru ini disebut PropertyChanged.Fody.
- Instruksi pengaturan Fody: http://code.google.com/p/fody/wiki/SampleUsage (ganti "Virtuosity" dengan "PropertyChanged")
- Situs proyek PropertyChanged.Fody: http://code.google.com/p/propertychanged/
Jika Anda lebih suka menggunakan NotifyPropertyWeaver (yang sedikit lebih mudah untuk menginstal, tetapi tidak akan selalu diperbarui di masa depan di luar perbaikan bug), di sini adalah situs proyek: http://code.google.com/p/ notifypropertyweaver /
Either way, solusi penenun IL ini benar-benar mengubah kalkulus dalam perdebatan antara model tampilan steroid vs konverter nilai.
sumber
BooleanToVisibility
mengambil satu nilai yang terkait dengan visibilitas (benar / salah) dan menerjemahkannya ke nilai lain. Ini tampaknya seperti penggunaan ideal aValueConverter
. Di sisi lain,MatchToVisibility
adalah penyandian logika bisnis diView
(jenis barang apa yang harus terlihat). Menurut pendapat saya logika ini harus didorong ke bawahViewModel
, atau lebih jauh ke dalam apa yang saya sebutEditModel
. Apa yang bisa dilihat pengguna harus berupa sesuatu yang sedang diuji.MatchToVisibility
tampaknya menjadi cara yang nyaman untuk mengaktifkan beberapa sakelar mode sederhana (saya memiliki satu tampilan khususnya dengan satu ton bagian yang dapat dinyalakan dan dimatikan. Dalam kebanyakan kasus, bagian tampilan bahkan diberi label (denganx:Name
) untuk mencocokkan dengan mode mereka berkorespondensi dengan.) Tidak benar-benar terjadi kepada saya bahwa ini adalah "logika bisnis", tetapi saya akan memberikan komentar Anda beberapa pemikiran.Jawaban:
Saya telah menggunakan
ValueConverters
dalam beberapa kasus dan memasukkan logika keViewModel
dalam yang lain. Perasaan saya adalah bahwa aValueConverter
menjadi bagian dariView
lapisan, jadi jika logikanya benar-benar bagian dariView
maka letakkan di sana, jika tidak letakkan diViewModel
.Secara pribadi saya tidak melihat masalah dengan
ViewModel
berurusan denganView
konsep-spesifik sepertiBrush
es karena dalam aplikasi sayaViewModel
hanya ada sebagai permukaan yang dapat diuji dan mengikat untukView
. Namun, beberapa orang menaruh banyak logika bisnis dalamViewModel
(saya tidak) dan dalam halViewModel
ini lebih seperti bagian dari lapisan bisnis mereka, jadi dalam hal ini saya tidak ingin hal-hal khusus WPF di sana.Saya lebih suka pemisahan yang berbeda:
View
- Hal-hal WPF, kadang-kadang tidak dapat diuji (seperti XAML dan kode di belakang) tetapi jugaValueConverter
sViewModel
- kelas yang dapat diuji dan bindable yang juga khusus untuk WPFEditModel
- bagian dari lapisan bisnis yang mewakili model saya selama manipulasiEntityModel
- bagian dari lapisan bisnis yang mewakili model saya sebagai bertahanRepository
- Bertanggung jawab atas kegigihanEntityModel
databaseJadi, cara saya melakukannya, saya punya sedikit kegunaan untuk
ValueConverter
sCara saya lolos dari beberapa "Con" Anda adalah membuat saya
ViewModel
sangat generik. Misalnya, yangViewModel
saya miliki, disebutChangeValueViewModel
mengimplementasikan properti Label dan properti Value. DiView
sana adaLabel
yang mengikat ke properti Label danTextBox
yang mengikat ke properti Value.Saya kemudian memiliki
ChangeValueView
yang merupakanDataTemplate
kunci dariChangeValueViewModel
jenis. Setiap kali WPF melihatViewModel
itu berlaku ituView
. Konstruktor sayaChangeValueViewModel
mengambil logika interaksi yang diperlukan untuk menyegarkan keadaannya dariEditModel
(biasanya hanya lewat aFunc<string>
) dan tindakan yang perlu diambil ketika pengguna mengedit Nilai (hanyaAction
yang menjalankan beberapa logika diEditModel
).Induk
ViewModel
(untuk layar) mengambilEditModel
dalam konstruktornya dan hanya instantiate s yang sesuaiViewModel
sepertiChangeValueViewModel
. Karena orang tuaViewModel
menyuntikkan tindakan untuk diambil saat pengguna melakukan perubahan, ia dapat mencegat semua tindakan ini dan mengambil tindakan lain. Oleh karena itu, tindakan edit yang disuntikkan untukChangeValueViewModel
mungkin terlihat seperti:Jelas bahwa
foreach
loop dapat dire-refored di tempat lain, tetapi yang dilakukan adalah mengambil tindakan, menerapkannya pada model, lalu (dengan asumsi model telah memperbarui keadaannya dengan cara yang tidak diketahui), memberi tahu semua anakViewModel
untuk pergi dan mendapatkan status mereka dari model lagi. Jika negara telah berubah, mereka bertanggung jawab untuk melaksanakanPropertyChanged
acara mereka , sebagaimana diperlukan.Itu menangani interaksi antara, katakanlah, kotak daftar dan panel detail dengan cukup baik. Ketika pengguna memilih pilihan baru, itu memperbarui
EditModel
dengan pilihan, danEditModel
mengubah nilai-nilai properti yang diekspos untuk panel detail. Anak-ViewModel
anak yang bertanggung jawab untuk menampilkan informasi panel detail secara otomatis diberitahu bahwa mereka perlu memeriksa nilai-nilai baru, dan jika mereka berubah, mereka memecatPropertyChanged
acara mereka .sumber
ViewModel
lapisan. Tidak semua orang setuju dengan saya, tetapi itu tergantung pada bagaimana arsitektur Anda bekerja.CalendarViewModel
untukCalendarView
UserControl, atau aDialogViewModel
untuk aDialogView
). Tapi itu hanya pendapat saya :)ViewModel
s saya .Jika Konversi adalah sesuatu yang Terkait-Tampilan, seperti memutuskan visibilitas objek, menentukan gambar yang akan ditampilkan, atau mencari tahu warna kuas apa yang akan digunakan, saya selalu meletakkan konverter saya di Tampilan.
Jika terkait bisnis, seperti menentukan apakah bidang harus ditutup, atau jika pengguna memiliki izin untuk melakukan tindakan, maka konversi terjadi di ViewModel saya.
Dari contoh Anda, saya pikir Anda kehilangan sepotong besar WPF:
DataTriggers
. Anda tampaknya menggunakan konverter untuk menentukan nilai bersyarat, tetapi konverter harus benar-benar untuk mengubah satu tipe data menjadi yang lain.Dalam contoh Anda di atas
Saya akan menggunakan
DataTrigger
untuk menentukan gambar yang akan ditampilkan, bukan aConverter
. Konverter adalah untuk mengonversi satu tipe data ke yang lain, sementara pemicu digunakan untuk menentukan beberapa properti berdasarkan nilai.Satu-satunya waktu saya akan mempertimbangkan menggunakan Konverter untuk ini adalah jika nilai terikat benar-benar berisi data gambar, dan saya perlu mengubahnya menjadi tipe data yang dapat dimengerti oleh UI. Misalnya, jika sumber data berisi properti yang dipanggil
ImageFilePath
, maka saya akan mempertimbangkan untuk menggunakan Konverter untuk mengkonversi string yang berisi lokasi file gambar keBitmapImage
yang dapat digunakan sebagai Sumber untuk Gambar sayaHasil akhirnya adalah saya memiliki satu perpustakaan namespace penuh dengan konverter generik yang mengkonversi satu tipe data ke yang lain, dan saya jarang harus kode konverter baru. Ada saat-saat ketika saya menginginkan konverter untuk konversi tertentu, tetapi jarang terjadi sehingga saya tidak keberatan menulisnya.
sumber
Grid
elemen. Saya juga berusaha melakukan hal-hal seperti mengatur kuas untuk latar depan / latar belakang / goresan berdasarkan data dalam model tampilan saya dan palet warna tertentu yang ditentukan dalam file konfigurasi. Saya tidak yakin ini sangat cocok untuk pemicu atau konverter. Satu-satunya masalah yang saya alami sejauh ini dengan menempatkan sebagian besar view logic dalam model view adalah menghubungkan semuaRaisePropertyChanged()
panggilan.DataTrigger
, bahkan mematikan elemen Grid. Biasanya saya menempatkan diContentControl
mana seharusnya konten dinamis saya dan menukar keluarContentTemplate
di pemicu. Saya punya contoh di tautan berikut jika Anda tertarik (gulir ke bawah ke bagian dengan tajukUsing a DataTrigger
) rachel53461.wordpress.com/2011/05/28/…<TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"
dan<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
Tergantung pada apa yang Anda uji , jika ada.
Tidak ada tes: intermix Lihat kode d / ViewModel sesuka hati (Anda selalu dapat refactor nanti).
Tes pada ViewModel dan / atau lebih rendah: gunakan konverter.
Tes pada layer Model dan / atau lebih rendah: intermix Lihat kode w / ViewModel sesuka hati
ViewModel mengabstraksi Model untuk Tampilan . Secara pribadi, saya akan menggunakan ViewModel untuk Brushes, dll dan lewati konverter. Tes pada lapisan di mana data berada dalam bentuk " paling murni " (mis. Lapisan Model ).
sumber
Visibility
,SolidColorBrush
, danThickness
.Ini mungkin tidak akan menyelesaikan semua masalah yang Anda sebutkan, tetapi ada dua hal yang perlu dipertimbangkan:
Pertama, Anda perlu menempatkan kode konverter di suatu tempat dalam strategi pertama Anda. Apakah Anda menganggap itu bagian dari tampilan atau model tampilan? Jika itu bagian dari tampilan, mengapa tidak menempatkan properti tampilan khusus dalam tampilan bukan model tampilan?
Kedua, sepertinya desain non-konverter Anda mencoba untuk memodifikasi properti objek aktual yang sudah ada. Sepertinya mereka sudah mengimplementasikan INotifyPropertyChanged, jadi mengapa tidak menggunakan buat objek wrapper khusus tampilan untuk diikat? Ini contoh sederhana:
sumber
Terkadang lebih baik menggunakan konverter nilai untuk memanfaatkan virtualisasi.
Contoh dari ini adalah apa dalam proyek di mana kami harus menampilkan data bitmasked untuk ratusan ribu sel dalam kotak. Ketika kami mendekodekan bitmask dalam model tampilan untuk setiap sel tunggal, program terlalu lama untuk memuat.
Tetapi ketika kami membuat konverter nilai yang menerjemahkan satu sel, program dimuat dalam sebagian kecil waktu dan sama responsifnya karena konverter hanya dipanggil ketika pengguna melihat sel tertentu (dan itu hanya perlu disebut maksimal tiga puluh kali setiap kali pengguna mengubah pandangan mereka di grid).
Saya tidak tahu bagaimana keluhan MVVM bahwa solusinya, tetapi mengurangi waktu muat sebesar 95%.
sumber