Apakah nilai konverter lebih banyak masalah daripada nilainya?

20

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 IValueConverteratau IMultiValueConverter, memberikan nilai atau nilai dari objectjenis 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
    • RaisePropertyChangedharus dipanggil untuk setiap properti turunan (lihat Pembaruan 2 di bawah)
    • 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 BoolToVisibilitydan apa yang saya sebut MatchToVisibilitydan 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 MatchToVisibilityadalah 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" FullNametanpa 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.

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.

penjahat
sumber
Hanya sebuah catatan: BooleanToVisibilitymengambil satu nilai yang terkait dengan visibilitas (benar / salah) dan menerjemahkannya ke nilai lain. Ini tampaknya seperti penggunaan ideal a ValueConverter. Di sisi lain, MatchToVisibilityadalah penyandian logika bisnis di View(jenis barang apa yang harus terlihat). Menurut pendapat saya logika ini harus didorong ke bawah ViewModel, atau lebih jauh ke dalam apa yang saya sebut EditModel. Apa yang bisa dilihat pengguna harus berupa sesuatu yang sedang diuji.
Scott Whitlock
@ Esc, itu poin yang bagus. Aplikasi yang saya kerjakan saat ini sebenarnya bukan aplikasi "bisnis", di mana ada tingkat izin yang berbeda untuk pengguna, jadi saya tidak berpikir seperti itu. MatchToVisibilitytampaknya 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.
devuxer
Contoh: misalkan Anda memiliki sistem stereo yang bisa dalam mode radio, CD, atau MP3. Asumsikan ada visual yang sesuai dengan setiap mode di berbagai bagian UI. Anda dapat (1) membiarkan tampilan memutuskan gambar mana yang sesuai dengan mode mana dan menghidupkan / mematikannya, (2) mengekspos properti pada model tampilan untuk setiap nilai mode (misalnya, IsModeRadio, IsModeCD), atau (3) mengekspos properti pada model tampilan untuk setiap elemen / grup grafis (misalnya, IsRadioLightOn, IsCDButtonGroupOn). (1) sepertinya cocok untuk pandangan saya, karena sudah memiliki mode mode. Apa yang Anda pikirkan dalam kasus ini?
devuxer
Ini adalah pertanyaan terpanjang, yang pernah saya lihat di seluruh SE! :]
trejder

Jawaban:

10

Saya telah menggunakan ValueConvertersdalam beberapa kasus dan memasukkan logika ke ViewModeldalam yang lain. Perasaan saya adalah bahwa a ValueConvertermenjadi bagian dari Viewlapisan, jadi jika logikanya benar-benar bagian dari Viewmaka letakkan di sana, jika tidak letakkan di ViewModel.

Secara pribadi saya tidak melihat masalah dengan ViewModelberurusan dengan Viewkonsep-spesifik seperti Brushes karena dalam aplikasi saya ViewModelhanya ada sebagai permukaan yang dapat diuji dan mengikat untuk View. Namun, beberapa orang menaruh banyak logika bisnis dalam ViewModel(saya tidak) dan dalam hal ViewModelini 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 juga ValueConverters
  • ViewModel - kelas yang dapat diuji dan bindable yang juga khusus untuk WPF
  • EditModel - bagian dari lapisan bisnis yang mewakili model saya selama manipulasi
  • EntityModel - bagian dari lapisan bisnis yang mewakili model saya sebagai bertahan
  • Repository- Bertanggung jawab atas kegigihan EntityModeldatabase

Jadi, cara saya melakukannya, saya punya sedikit kegunaan untuk ValueConverters

Cara saya lolos dari beberapa "Con" Anda adalah membuat saya ViewModelsangat generik. Misalnya, yang ViewModelsaya miliki, disebut ChangeValueViewModelmengimplementasikan properti Label dan properti Value. Di Viewsana ada Labelyang mengikat ke properti Label dan TextBoxyang mengikat ke properti Value.

Saya kemudian memiliki ChangeValueViewyang merupakan DataTemplatekunci dari ChangeValueViewModeljenis. Setiap kali WPF melihat ViewModelitu berlaku itu View. Konstruktor saya ChangeValueViewModelmengambil logika interaksi yang diperlukan untuk menyegarkan keadaannya dari EditModel(biasanya hanya lewat a Func<string>) dan tindakan yang perlu diambil ketika pengguna mengedit Nilai (hanya Actionyang menjalankan beberapa logika di EditModel).

Induk ViewModel(untuk layar) mengambil EditModeldalam konstruktornya dan hanya instantiate s yang sesuai ViewModelseperti ChangeValueViewModel. Karena orang tua ViewModelmenyuntikkan tindakan untuk diambil saat pengguna melakukan perubahan, ia dapat mencegat semua tindakan ini dan mengambil tindakan lain. Oleh karena itu, tindakan edit yang disuntikkan untuk ChangeValueViewModelmungkin terlihat seperti:

(string newValue) =>
{
    editModel.SomeField = newValue;
    foreach(var childViewModel in this.childViewModels)
    {
        childViewModel.RefreshStateFromEditModel();
    }
}

Jelas bahwa foreachloop 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 anak ViewModeluntuk pergi dan mendapatkan status mereka dari model lagi. Jika negara telah berubah, mereka bertanggung jawab untuk melaksanakan PropertyChangedacara mereka , sebagaimana diperlukan.

Itu menangani interaksi antara, katakanlah, kotak daftar dan panel detail dengan cukup baik. Ketika pengguna memilih pilihan baru, itu memperbarui EditModeldengan pilihan, dan EditModelmengubah nilai-nilai properti yang diekspos untuk panel detail. Anak- ViewModelanak yang bertanggung jawab untuk menampilkan informasi panel detail secara otomatis diberitahu bahwa mereka perlu memeriksa nilai-nilai baru, dan jika mereka berubah, mereka memecat PropertyChangedacara mereka .

Scott Whitlock
sumber
/anggukan. Itu sangat mirip dengan bagaimana penampilanku.
Ian
+1. Terima kasih atas jawaban Anda, Scott, saya memiliki hampir semua lapisan yang Anda lakukan, dan saya juga tidak memasukkan logika bisnis dalam model tampilan. (Sebagai catatan, saya menggunakan EntityFramework Code First, dan saya memiliki lapisan layanan yang menerjemahkan antara model tampilan dan model entitas, dan sebaliknya.) Jadi, mengingat ini, saya kira Anda mengatakan tidak ada banyak biaya untuk meletakkan semua / sebagian besar logika konversi di lapisan model tampilan.
devuxer
@DANM - Ya, saya setuju. Saya lebih suka konversi di ViewModellapisan. Tidak semua orang setuju dengan saya, tetapi itu tergantung pada bagaimana arsitektur Anda bekerja.
Scott Whitlock
2
Saya akan mengatakan +1 setelah membaca paragraf pertama, tetapi kemudian saya membaca paragraf ke-2 Anda dan sangat tidak setuju dengan meletakkan kode khusus tampilan di ViewModels. Satu-satunya pengecualian adalah jika ViewModel dibuat khusus untuk berada di belakang Tampilan generik (seperti CalendarViewModeluntuk CalendarViewUserControl, atau a DialogViewModeluntuk a DialogView). Tapi itu hanya pendapat saya :)
Rachel
@ Rachel - yah, jika Anda terus membaca melewati paragraf kedua saya, Anda akan melihat itulah yang saya lakukan. :) Tidak ada logika bisnis di ViewModels saya .
Scott Whitlock
8

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

Contoh: misalkan Anda memiliki sistem stereo yang bisa dalam mode radio, CD, atau MP3. Asumsikan ada visual yang sesuai dengan setiap mode di berbagai bagian UI. Anda dapat (1) membiarkan tampilan memutuskan gambar mana yang sesuai dengan mode mana dan menghidupkan / mematikannya, (2) mengekspos properti pada model tampilan untuk setiap nilai mode (misalnya, IsModeRadio, IsModeCD), atau (3) mengekspos properti pada model tampilan untuk setiap elemen / grup grafis (misalnya, IsRadioLightOn, IsCDButtonGroupOn). (1) sepertinya cocok untuk pandangan saya, karena sudah memiliki mode mode. Apa yang Anda pikirkan dalam kasus ini?

Saya akan menggunakan DataTriggeruntuk menentukan gambar yang akan ditampilkan, bukan a Converter. Konverter adalah untuk mengonversi satu tipe data ke yang lain, sementara pemicu digunakan untuk menentukan beberapa properti berdasarkan nilai.

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{StaticResource RadioImage}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Mode}" Value="CD">
            <Setter Property="Source" Value="{StaticResource CDImage}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Mode}" Value="MP3">
            <Setter Property="Source" Value="{StaticResource MP3Image}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

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 ke BitmapImageyang dapat digunakan sebagai Sumber untuk Gambar saya

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{Binding ImageFilePath, 
            Converter={StaticResource StringPathToBitmapConverter}}" />
</Style>

Hasil 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.

Rachel
sumber
+1. Anda menaikkan beberapa poin bagus. Saya telah menggunakan pemicu sebelumnya, tetapi dalam kasus saya, saya tidak mengganti sumber gambar (yang merupakan properti), saya mematikan seluruh Gridelemen. 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 semua RaisePropertyChanged()panggilan.
devuxer
@ Danan Saya benar-benar akan melakukan semua hal itu dalam DataTrigger, bahkan mematikan elemen Grid. Biasanya saya menempatkan di ContentControlmana seharusnya konten dinamis saya dan menukar keluar ContentTemplatedi pemicu. Saya punya contoh di tautan berikut jika Anda tertarik (gulir ke bawah ke bagian dengan tajuk Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel
Saya telah menggunakan templat data dan kontrol konten sebelumnya, tetapi saya tidak pernah membutuhkan pemicu karena saya selalu memiliki model tampilan unik untuk setiap tampilan. Bagaimanapun, teknik Anda masuk akal dan cukup elegan, tetapi juga sangat bertele-tele. Dengan MatchToVisibility, bisa disingkat menjadi ini: <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}}"
devuxer
1

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 ).

Jake Berger
sumber
2
Poin menarik tentang pengujian, tapi saya rasa saya tidak melihat bagaimana memiliki logika konverter dalam model tampilan membahayakan testability model tampilan? Saya tentu saja tidak menyarankan menempatkan kontrol UI yang sebenarnya dalam model tampilan. Hanya melihat-spesifik sifat seperti Visibility, SolidColorBrush, dan Thickness.
devuxer
@DanM: Jika Anda menggunakan pendekatan Lihat-pertama , maka tidak ada masalah . Namun, beberapa menggunakan pendekatan ViewModel-first di mana ViewModel merujuk View, itu mungkin bermasalah .
Jake Berger
Hai Jay, jelas pendekatan pandangan pertama. Tampilan tidak tahu apa pun tentang model tampilan kecuali untuk nama properti yang harus diikat. Terima kasih telah menindaklanjuti. +1.
devuxer
0

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:

public class RealData
{
    private bool mIsInteresting;
    public bool IsInteresting
    {
        get { return mIsInteresting; }
        set 
        {
            if (mIsInteresting != null) 
            {
                mIsInteresting = value;
                RaisePropertyChanged("IsInteresting");
            }
        }
    }
}

public class RealDataView
{
    private RealData mRealData;

    public RealDataView(RealData data)
    {
        mRealData = data;
        mRealData.PropertyChanged += OnRealDataPropertyChanged;
    }

    public Visibility IsVisiblyInteresting
    {
       get { return mRealData.IsInteresting ? Visibility.Visible : Visibility.Hidden; }
       set { mRealData.IsInteresting = (value == Visibility.Visible); }
    }

    private void OnRealDataPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsInteresting") 
        {
            RaisePropertyChanged(this, "IsVisiblyInteresting");
        }
    }
}
John Fisher
sumber
Saya tidak bermaksud mengatakan bahwa saya mengubah properti model entitas saya secara langsung dalam tampilan atau model tampilan. Model tampilan jelas merupakan lapisan yang berbeda dari lapisan model entitas saya. Bahkan, pekerjaan yang saya lakukan sejauh ini hanya pada tampilan baca-saja. Ini bukan untuk mengatakan bahwa aplikasi saya tidak akan melibatkan pengeditan apa pun, tetapi saya tidak melihat konversi dilakukan pada kontrol yang digunakan untuk pengeditan (jadi anggap semua binding adalah satu arah, kecuali untuk pilihan dalam daftar). Poin bagus tentang "tampilan data". Itu adalah konsep yang diangkat dalam posting murid XAML yang saya sebutkan di bagian atas pertanyaan saya.
devuxer
0

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%.

kleineg
sumber