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.
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 (dengan x: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.
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.