Là những người chuyển đổi giá trị rắc rối hơn giá trị của họ?


20

Tôi đang làm việc trên một ứng dụng WPF với các khung nhìn yêu cầu nhiều chuyển đổi giá trị. Ban đầu, triết lý của tôi (được truyền cảm hứng một phần bởi cuộc tranh luận sôi nổi này về các môn đệ XAML ) là tôi nên làm cho mô hình khung nhìn nghiêm túc về việc hỗ trợ các yêu cầu dữ liệu của chế độ xem. Điều này có nghĩa là bất kỳ chuyển đổi giá trị nào được yêu cầu để biến dữ liệu thành những thứ như khả năng hiển thị, cọ vẽ, kích thước, v.v. sẽ được xử lý bằng các trình biến đổi giá trị và trình biến đổi đa giá trị. Về mặt khái niệm, điều này có vẻ khá thanh lịch. Mô hình khung nhìn và khung nhìn sẽ có một mục đích riêng biệt và được tách riêng. Một đường rõ ràng sẽ được rút ra giữa "dữ liệu" và "nhìn".

Chà, sau khi đưa ra chiến lược này "cố gắng đại học cũ", tôi có một số nghi ngờ liệu tôi có muốn tiếp tục phát triển theo cách này hay không. Tôi thực sự đang cân nhắc việc bán phá giá các bộ chuyển đổi giá trị và đặt trách nhiệm cho (gần như) tất cả các chuyển đổi giá trị trong tay của mô hình xem.

Thực tế của việc sử dụng các bộ chuyển đổi giá trị dường như không thể đo lường được giá trị rõ ràng của các mối quan tâm được phân tách rõ ràng. Vấn đề lớn nhất của tôi với các công cụ chuyển đổi giá trị là chúng rất tẻ nhạt khi sử dụng. Bạn phải tạo một lớp mới, triển khai IValueConverterhoặc IMultiValueConverter, truyền giá trị hoặc giá trị từ objectđúng loại, kiểm tra DependencyProperty.Unset(ít nhất là cho các trình biến đổi đa giá trị), viết logic chuyển đổi, đăng ký trình chuyển đổi trong từ điển tài nguyên [xem cập nhật bên dưới ] và cuối cùng, kết nối trình chuyển đổi bằng XAML khá dài (yêu cầu sử dụng chuỗi ma thuật cho cả (các) ràng buộc và tên của trình chuyển đổi[xem cập nhật bên dưới]). Quá trình gỡ lỗi cũng không phải là dã ngoại, vì các thông báo lỗi thường khó hiểu, đặc biệt là trong chế độ thiết kế / Expression Blend của Visual Studio.

Điều này không có nghĩa là sự thay thế - làm cho mô hình khung nhìn chịu trách nhiệm cho tất cả chuyển đổi giá trị - là một sự cải tiến. Điều này rất có thể là một vấn đề của cỏ xanh hơn ở phía bên kia. Bên cạnh việc mất đi sự tách biệt giữa các mối quan tâm, bạn phải viết một loạt các thuộc tính có nguồn gốc và đảm bảo rằng bạn gọi một cách tận tâm RaisePropertyChanged(() => DerivedProperty)khi thiết lập các thuộc tính cơ sở, điều này có thể chứng tỏ là một vấn đề bảo trì khó chịu.

Sau đây là danh sách ban đầu tôi tổng hợp các ưu và nhược điểm của việc cho phép các mô hình xem xử lý logic chuyển đổi và loại bỏ các trình biến đổi giá trị:

  • Ưu điểm:
    • Tổng số ràng buộc ít hơn kể từ khi đa chuyển đổi được loại bỏ
    • Ít chuỗi ma thuật hơn (đường dẫn ràng buộc + tên tài nguyên chuyển đổi )
    • Không còn đăng ký mỗi bộ chuyển đổi (cộng với duy trì danh sách này)
    • Ít công việc hơn để viết từng trình chuyển đổi (không yêu cầu giao diện triển khai hoặc yêu cầu truyền)
    • Có thể dễ dàng thêm các phụ thuộc để trợ giúp với các chuyển đổi (ví dụ: bảng màu)
    • Đánh dấu XAML ít dài dòng và dễ đọc hơn
    • Tái sử dụng chuyển đổi vẫn có thể (mặc dù một số kế hoạch là bắt buộc)
    • Không có vấn đề bí ẩn nào với DependencyProperty.Unset (một vấn đề tôi nhận thấy với các trình chuyển đổi đa giá trị)

* Strikethroughs chỉ ra các lợi ích sẽ biến mất nếu bạn sử dụng tiện ích mở rộng đánh dấu (xem cập nhật bên dưới)

  • Nhược điểm:
    • Khớp nối mạnh hơn giữa mô hình khung nhìn và khung nhìn (ví dụ: các thuộc tính phải xử lý các khái niệm như khả năng hiển thị và cọ vẽ)
    • Tổng số thuộc tính để cho phép ánh xạ trực tiếp cho mọi ràng buộc trong chế độ xem
    • RaisePropertyChangedphải được gọi cho mỗi thuộc tính dẫn xuất (xem Cập nhật 2 bên dưới)
    • Vẫn phải dựa vào bộ chuyển đổi nếu chuyển đổi dựa trên thuộc tính của thành phần UI

Vì vậy, như bạn có thể nói, tôi có một chút đau lòng về vấn đề này. Tôi rất do dự khi đi vào con đường tái cấu trúc chỉ để nhận ra rằng quy trình mã hóa cũng không hiệu quả và tẻ nhạt cho dù tôi sử dụng các trình biến đổi giá trị hay phơi bày nhiều thuộc tính chuyển đổi giá trị trong mô hình xem của tôi.

Tôi có thiếu bất kỳ ưu / nhược điểm nào không? Đối với những người đã thử cả hai phương tiện chuyển đổi giá trị, bạn thấy công việc nào tốt hơn cho mình và tại sao? Còn lựa chọn nào nữa ko? (Các môn đệ đã đề cập vài điều về các nhà cung cấp mô tả kiểu, nhưng tôi không thể hiểu được những gì họ đang nói. Bất kỳ cái nhìn sâu sắc nào về điều này sẽ được đánh giá cao.)


Cập nhật

Hôm nay tôi phát hiện ra rằng có thể sử dụng một thứ gọi là "phần mở rộng đánh dấu" để loại bỏ nhu cầu đăng ký bộ chuyển đổi giá trị. Trong thực tế, nó không chỉ loại bỏ sự cần thiết phải đăng ký chúng, mà nó thực sự cung cấp intellisense để chọn một trình chuyển đổi khi bạn gõ Converter=. Đây là bài viết giúp tôi bắt đầu: http://www.wpftutorial.net/ValueConverters.html .

Khả năng sử dụng tiện ích mở rộng đánh dấu thay đổi phần nào sự cân bằng trong danh sách ưu và nhược điểm của tôi và thảo luận ở trên (xem phần gạch ngang).

Kết quả của sự mặc khải này, tôi đang thử nghiệm một hệ thống kết hợp nơi tôi sử dụng các trình chuyển đổi cho BoolToVisibilityvà những gì tôi gọi MatchToVisibilityvà mô hình xem cho tất cả các chuyển đổi khác. MatchToVisibility về cơ bản là một trình chuyển đổi cho phép tôi kiểm tra xem giá trị ràng buộc (thường là enum) có khớp với một hoặc nhiều giá trị được chỉ định trong XAML không.

Thí dụ:

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"

Về cơ bản những gì nó làm là kiểm tra xem trạng thái đã kết thúc hay đã hủy. Nếu đúng như vậy, thì khả năng hiển thị sẽ được đặt thành "Hiển thị". Mặt khác, nó được đặt thành "Ẩn". Đây hóa ra là một kịch bản rất phổ biến và việc bộ chuyển đổi này đã tiết kiệm cho tôi khoảng 15 thuộc tính trên mô hình chế độ xem của tôi (cộng với các câu lệnh RaisePropertyChanged liên quan). Lưu ý rằng khi bạn nhập Converter={vc:, "MatchToVisibility" sẽ hiển thị trong menu intellisense. Điều này đáng chú ý làm giảm khả năng xảy ra lỗi và làm cho việc sử dụng các trình biến đổi giá trị trở nên ít tẻ nhạt hơn (bạn không cần phải nhớ hoặc tra cứu tên của trình chuyển đổi giá trị mà bạn muốn).

Trong trường hợp bạn tò mò, tôi sẽ dán mã bên dưới. Một đặc điểm quan trọng của thực hiện này MatchToVisibilitylà nó sẽ kiểm tra xem nếu giá trị ràng buộc là một enum, và nếu có, nó sẽ kiểm tra để chắc chắn Value1, Value2vv cũng enums cùng loại. Điều này cung cấp kiểm tra thời gian thiết kế và thời gian chạy xem liệu có bất kỳ giá trị enum nào bị nhầm lẫn hay không. Để cải thiện điều này thành kiểm tra thời gian biên dịch, bạn có thể sử dụng cách sau đây (tôi đã gõ bằng tay vì vậy xin vui lòng tha thứ cho tôi nếu tôi mắc lỗi nào):

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

Mặc dù điều này an toàn hơn, nhưng nó quá dài dòng để xứng đáng với tôi. Tôi cũng có thể chỉ sử dụng một thuộc tính trên mô hình khung nhìn nếu tôi sẽ làm điều này. Dù sao, tôi thấy rằng việc kiểm tra thời gian thiết kế là hoàn toàn phù hợp với các kịch bản tôi đã thử cho đến nay.

Đây là mã cho 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;
    }
}

Đây là mã cho 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();
    }
}

Đây là phương pháp mở rộng ToEnum

public static TEnum ToEnum<TEnum>(this string text)
{
    return (TEnum)Enum.Parse(typeof(TEnum), text);
}

Cập nhật 2

Vì tôi đã đăng câu hỏi này, tôi đã bắt gặp một dự án nguồn mở sử dụng "dệt IL" để tiêm mã NotifyPropertyChanged cho các thuộc tính và thuộc tính phụ thuộc. Điều này làm cho việc thực hiện tầm nhìn của Josh Smith về mô hình xem như là một "công cụ chuyển đổi giá trị trên steroid" một cách dễ dàng. Bạn chỉ có thể sử dụng "Thuộc tính được triển khai tự động" và thợ dệt sẽ làm phần còn lại.

Thí dụ:

Nếu tôi nhập mã này:

public string GivenName { get; set; }
public string FamilyName { get; set; }

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

... đây là những gì được biên dịch:

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

Đó là một khoản tiết kiệm rất lớn về số lượng mã bạn phải nhập, đọc, cuộn qua, v.v. Quan trọng hơn, mặc dù vậy, nó giúp bạn không phải tìm ra sự phụ thuộc của bạn là gì. Bạn có thể thêm "tài sản được" như thế FullNamemà không cần phải cố gắng đi lên chuỗi phụ thuộc để thêm vào RaisePropertyChanged()các cuộc gọi.

Dự án nguồn mở này được gọi là gì? Phiên bản gốc có tên là "NotifyPropertyWeaver", nhưng chủ sở hữu (Simon Potter) đã tạo ra một nền tảng gọi là "Fody" để lưu trữ cả loạt máy dệt IL. Tương đương với NotifyPropertyWeaver trong nền tảng mới này được gọi là PropertyChanged.Fody.

Nếu bạn muốn sử dụng NotifyPropertyWeaver (cài đặt đơn giản hơn một chút, nhưng sẽ không nhất thiết phải được cập nhật trong tương lai ngoài sửa lỗi), đây là trang web của dự án: http://code.google.com.vn/p/ notifypropertyweaver /

Dù bằng cách nào, các giải pháp máy dệt IL này thay đổi hoàn toàn phép tính trong cuộc tranh luận giữa mô hình xem trên steroid so với các bộ biến đổi giá trị.


Chỉ cần một lưu ý: BooleanToVisibilitylấy một giá trị liên quan đến khả năng hiển thị (đúng / sai) và chuyển nó thành một giá trị khác. Đây có vẻ như là một cách sử dụng lý tưởng của a ValueConverter. Mặt khác, MatchToVisibilitymã hóa logic nghiệp vụ trong View(loại mặt hàng nào sẽ hiển thị). Theo ý kiến ​​của tôi, logic này nên được đẩy xuống ViewModel, hoặc thậm chí xa hơn vào cái mà tôi gọi là EditModel. Những gì người dùng có thể thấy nên là một cái gì đó đang thử nghiệm.
Scott Whitlock

@Scott, đó là một điểm tốt. Ứng dụng tôi đang làm việc hiện tại không thực sự là một ứng dụng "kinh doanh", nơi có các mức cấp phép khác nhau cho người dùng, vì vậy tôi đã không suy nghĩ theo những dòng đó. MatchToVisibilitydường như là một cách thuận tiện để bật một số công tắc chế độ đơn giản (tôi có một chế độ xem cụ thể với rất nhiều bộ phận có thể bật và tắt. Trong hầu hết các trường hợp, các phần của chế độ xem thậm chí được gắn nhãn (với x:Name) để khớp với chế độ chúng tương ứng với.) Tôi thực sự không nghĩ rằng đây là "logic kinh doanh", nhưng tôi sẽ đưa ra nhận xét của bạn.
devuxer

Ví dụ: giả sử bạn có một hệ thống âm thanh nổi có thể ở chế độ radio, CD hoặc MP3. Giả sử có hình ảnh tương ứng với từng chế độ trong các phần khác nhau của giao diện người dùng. Bạn có thể (1) để chế độ xem quyết định đồ họa tương ứng với chế độ nào và bật / tắt chúng cho phù hợp, (2) hiển thị các thuộc tính trên mô hình chế độ xem cho từng giá trị chế độ (ví dụ: IsModeRadio, IsModeCD) hoặc (3) phơi bày các thuộc tính trên mô hình khung nhìn cho từng thành phần / nhóm đồ họa (ví dụ: IsRadioLightOn, IsCDButtongroupOn). (1) có vẻ phù hợp tự nhiên với quan điểm của tôi, bởi vì nó đã có nhận thức về chế độ. Bạn nghĩ gì trong trường hợp này?
devuxer

Đây là câu hỏi dài nhất, tôi từng thấy trong toàn bộ SE! :]
trejder

Câu trả lời:


10

Tôi đã sử dụng ValueConverterstrong một số trường hợp và đưa logic vào ViewModeltrong những người khác. Cảm giác của tôi là ValueConvertertrở thành một phần của Viewlớp, vì vậy nếu logic thực sự là một phần của Viewnó thì hãy đặt nó ở đó, nếu không thì đặt nó vào ViewModel.

Cá nhân tôi không thấy có vấn đề gì với ViewModelviệc xử lý Viewcác khái niệm cụ thể như Brushes bởi vì trong các ứng dụng của tôi ViewModelchỉ tồn tại như một bề mặt có thể kiểm tra và ràng buộc cho View. Tuy nhiên, một số người đặt rất nhiều logic kinh doanh vào ViewModel(tôi không) và trong trường hợp đó, ViewModelnó giống như một phần của lớp kinh doanh của họ, vì vậy trong trường hợp đó tôi sẽ không muốn những thứ cụ thể của WPF ở đó.

Tôi thích một sự tách biệt khác nhau:

  • View- Công cụ WPF, đôi khi không thể kiểm chứng (như XAML và mã phía sau) nhưng cũng ValueConverters
  • ViewModel - lớp có thể kiểm tra và ràng buộc cũng đặc trưng cho WPF
  • EditModel - một phần của lớp nghiệp vụ đại diện cho mô hình của tôi trong quá trình thao tác
  • EntityModel - một phần của lớp nghiệp vụ đại diện cho mô hình của tôi vẫn tồn tại
  • Repository- chịu trách nhiệm cho sự kiên trì của EntityModelcơ sở dữ liệu

Vì vậy, cách tôi làm, tôi có ít sử dụng cho ValueConverters

Cách tôi tránh xa một số "Con" của bạn là làm cho tôi ViewModelrất chung chung. Chẳng hạn, một cái ViewModeltôi có, được gọi là ChangeValueViewModelthực hiện thuộc tính Nhãn và thuộc tính Giá trị. Trên Viewđó có một Labelliên kết với thuộc tính Nhãn và một TextBoxliên kết với thuộc tính Giá trị.

Sau đó tôi có một ChangeValueViewloại DataTemplatekhóa ChangeValueViewModel. Bất cứ khi nào WPF thấy rằng ViewModelnó áp dụng điều đó View. Hàm tạo của tôi ChangeValueViewModellấy logic tương tác mà nó cần để làm mới trạng thái của nó từ EditModel(thường chỉ truyền vào a Func<string>) và hành động cần thực hiện khi người dùng chỉnh sửa Giá trị (chỉ Actionthực hiện một số logic trong EditModel).

Cha mẹ ViewModel(cho màn hình) lấy một EditModeltrong hàm tạo của nó và chỉ khởi tạo các ViewModels sơ cấp thích hợp như ChangeValueViewModel. Vì cha mẹ ViewModelđang thực hiện hành động cần thực hiện khi người dùng thực hiện bất kỳ thay đổi nào, nên nó có thể chặn tất cả các hành động này và thực hiện các hành động khác. Do đó, hành động chỉnh sửa được tiêm cho ChangeValueViewModelcó thể trông giống như:

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

Rõ ràng foreachvòng lặp có thể được tái cấu trúc ở nơi khác, nhưng hành động này là gì, áp dụng nó cho mô hình, sau đó (giả sử mô hình đã cập nhật trạng thái của nó theo một cách nào đó), nói với tất cả trẻ em ViewModelđi và lấy trạng thái của chúng từ mô hình một lần nữa. Nếu nhà nước đã thay đổi, họ có trách nhiệm thực hiện các PropertyChangedsự kiện của họ , khi cần thiết.

Điều đó xử lý sự tương tác giữa, giả sử, một hộp danh sách và bảng chi tiết khá độc đáo. Khi người dùng chọn một lựa chọn mới, nó sẽ cập nhật EditModellựa chọn đó và EditModelthay đổi các giá trị của các thuộc tính được hiển thị cho bảng chi tiết. Những ViewModelđứa trẻ chịu trách nhiệm hiển thị thông tin bảng chi tiết sẽ tự động được thông báo rằng chúng cần kiểm tra các giá trị mới và nếu chúng thay đổi, chúng sẽ kích hoạt PropertyChangedcác sự kiện của chúng .


/ gật đầu. Điều đó khá giống với vẻ ngoài của tôi.
Ian

+1. Cảm ơn câu trả lời của bạn, Scott, tôi có khá nhiều lớp giống như bạn làm và tôi cũng không đưa logic kinh doanh vào mô hình xem. (Đối với bản ghi, tôi đang sử dụng Mã EntityFramework Trước tiên và tôi có một lớp dịch vụ dịch giữa các mô hình xem và mô hình thực thể và ngược lại.) để đặt tất cả / hầu hết logic chuyển đổi trong lớp mô hình xem.
devuxer

@DanM - Vâng, tôi đồng ý. Tôi muốn chuyển đổi trong ViewModellớp. Không phải ai cũng đồng ý với tôi, nhưng nó phụ thuộc vào cách kiến ​​trúc của bạn hoạt động.
Scott Whitlock

2
Tôi sẽ nói +1 sau khi đọc đoạn đầu tiên, nhưng sau đó tôi đọc đoạn thứ 2 của bạn và hoàn toàn không đồng ý với việc đặt mã cụ thể của chế độ xem trong ViewModels. Một ngoại lệ là nếu ViewModel được tạo riêng để đi phía sau Chế độ xem chung (chẳng hạn như CalendarViewModelđối với CalendarViewUserControl hoặc DialogViewModelđối với a DialogView). Đó chỉ là ý kiến ​​của tôi thôi :)
Rachel

@Rachel - tốt, nếu bạn tiếp tục đọc qua đoạn thứ hai của tôi, bạn sẽ thấy đó chính xác là những gì tôi đang làm. :) Không có logic kinh doanh trong ViewModels của tôi .
Scott Whitlock

8

Nếu Chuyển đổi là một cái gì đó liên quan đến Chế độ xem, chẳng hạn như quyết định mức độ hiển thị của đối tượng, xác định hình ảnh nào sẽ hiển thị hoặc tìm ra màu cọ nào sẽ sử dụng, tôi luôn đặt bộ chuyển đổi của mình trong Chế độ xem.

Nếu Liên quan đến doanh nghiệp của nó, chẳng hạn như xác định xem một trường có bị che hay không, hoặc nếu người dùng có quyền thực hiện một hành động, thì chuyển đổi sẽ xảy ra trong ViewModel của tôi.

Từ các ví dụ của bạn, tôi nghĩ rằng bạn đang thiếu một phần lớn WPF : DataTriggers. Bạn dường như đang sử dụng các trình chuyển đổi để xác định các giá trị có điều kiện, nhưng các trình chuyển đổi thực sự nên để chuyển đổi một loại dữ liệu thành một loại dữ liệu khác.

Trong ví dụ của bạn ở trên

Ví dụ: giả sử bạn có một hệ thống âm thanh nổi có thể ở chế độ radio, CD hoặc MP3. Giả sử có hình ảnh tương ứng với từng chế độ trong các phần khác nhau của giao diện người dùng. Bạn có thể (1) để chế độ xem quyết định đồ họa tương ứng với chế độ nào và bật / tắt chúng cho phù hợp, (2) hiển thị các thuộc tính trên mô hình chế độ xem cho từng giá trị chế độ (ví dụ: IsModeRadio, IsModeCD) hoặc (3) phơi bày các thuộc tính trên mô hình khung nhìn cho từng thành phần / nhóm đồ họa (ví dụ: IsRadioLightOn, IsCDButtongroupOn). (1) có vẻ phù hợp tự nhiên với quan điểm của tôi, bởi vì nó đã có nhận thức về chế độ. Bạn nghĩ gì trong trường hợp này?

Tôi sẽ sử dụng a DataTriggerđể xác định hình ảnh nào sẽ hiển thị, không phải a Converter. Bộ chuyển đổi là để chuyển đổi một loại dữ liệu sang loại khác, trong khi bộ kích hoạt được sử dụng để xác định một số thuộc tính dựa trên một giá trị.

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

Lần duy nhất tôi sẽ cân nhắc sử dụng Trình chuyển đổi cho việc này là nếu giá trị ràng buộc thực sự chứa dữ liệu hình ảnh và tôi cần chuyển đổi nó thành loại dữ liệu mà UI có thể hiểu được. Ví dụ: nếu nguồn dữ liệu có chứa một thuộc tính được gọi ImageFilePath, tôi sẽ xem xét sử dụng Trình chuyển đổi để chuyển đổi chuỗi chứa vị trí tệp hình ảnh thành một BitmapImagethuộc tính có thể được sử dụng làm Nguồn cho Hình ảnh của tôi

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

Kết quả cuối cùng là tôi có một không gian tên thư viện chứa đầy các bộ chuyển đổi chung để chuyển đổi một loại dữ liệu này sang loại khác và tôi hiếm khi phải mã hóa một trình chuyển đổi mới. Có những lúc tôi sẽ muốn các trình chuyển đổi cho các chuyển đổi cụ thể, nhưng chúng không thường xuyên đến mức tôi không bao giờ bận tâm đến việc viết chúng.


+1. Bạn nêu lên một số điểm tốt. Tôi đã sử dụng các kích hoạt trước đây, nhưng trong trường hợp của tôi, tôi không chuyển đổi nguồn hình ảnh (vốn là một tài sản), tôi đang chuyển đổi toàn bộ Gridcác yếu tố. Tôi cũng đang cố gắng thực hiện những việc như đặt cọ vẽ cho tiền cảnh / hậu cảnh / nét dựa trên dữ liệu trong mô hình chế độ xem của tôi và bảng màu cụ thể được xác định trong tệp cấu hình. Tôi không chắc đây là một sự phù hợp tuyệt vời cho cả trình kích hoạt hay trình chuyển đổi. Vấn đề duy nhất tôi gặp phải từ trước đến nay với việc đưa hầu hết logic xem trong mô hình khung nhìn là kết nối tất cả các RaisePropertyChanged()cuộc gọi.
devuxer

@DanM Tôi thực sự sẽ làm tất cả những điều đó trong một DataTrigger, thậm chí chuyển đổi các phần tử của Grid. Thông thường tôi đặt một ContentControlnơi mà nội dung động của tôi sẽ được và trao đổi ContentTemplatetrong một kích hoạt. Tôi có một ví dụ tại liên kết sau đây nếu bạn quan tâm đến (di chuyển xuống phần với tiêu đề của Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/...
Rachel

Trước đây tôi đã sử dụng các mẫu dữ liệu và kiểm soát nội dung, nhưng tôi chưa bao giờ cần kích hoạt vì tôi luôn có một mô hình chế độ xem duy nhất cho mỗi chế độ xem. Dù sao, kỹ thuật của bạn có ý nghĩa hoàn hảo và khá thanh lịch, nhưng nó cũng rất dài dòng. Với MatchToVisibility, nó có thể được rút ngắn về điều này: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer

1

Nó phụ thuộc vào những gì bạn đang thử nghiệm , nếu bất cứ điều gì.

Không có kiểm tra: intermix Xem mã w / ViewModel theo ý muốn (bạn luôn có thể cấu trúc lại sau).
Các thử nghiệm trên ViewModel và / hoặc thấp hơn: sử dụng các bộ chuyển đổi.
Thử nghiệm trên các lớp Model và / hoặc thấp hơn: intermix Xem mã w / ViewModel tùy ý

ViewModel trừu tượng hóa Mô hình cho Chế độ xem . Cá nhân, tôi sử dụng ViewModel cho Brush, v.v. và bỏ qua các trình chuyển đổi. Kiểm tra (các) lớp trong đó dữ liệu ở dạng " tinh khiết nhất " (nghĩa là các lớp Mô hình ).


2
Điểm thú vị về thử nghiệm, nhưng tôi đoán tôi không thấy việc có logic chuyển đổi trong mô hình khung nhìn gây hại cho khả năng kiểm tra của mô hình khung nhìn? Tôi chắc chắn không đề xuất đưa các điều khiển UI thực tế vào mô hình xem. Chỉ cần xem cụ thể thuộc tính thích Visibility, SolidColorBrushThickness.
devuxer

@DanM: Nếu bạn đang sử dụng phương pháp Xem trước , thì không vấn đề gì . Tuy nhiên, một số sử dụng cách tiếp cận đầu tiên của ViewModel trong đó ViewModel tham chiếu đến View, nó có thể có vấn đề .
Jake Berger

Xin chào Jay, chắc chắn là một cách tiếp cận đầu tiên. Khung nhìn không biết gì về mô hình khung nhìn ngoại trừ tên của các thuộc tính mà nó cần liên kết. Cảm ơn đã theo dõi. +1.
devuxer

0

Điều này có lẽ sẽ không giải quyết được tất cả các vấn đề bạn đề cập, nhưng có hai điểm cần xem xét:

Đầu tiên, bạn cần đặt mã chuyển đổi ở đâu đó trong chiến lược đầu tiên của bạn. Bạn có xem xét một phần của khung nhìn hoặc mô hình khung nhìn không? Nếu đó là một phần của chế độ xem, tại sao không đặt các thuộc tính dành riêng cho chế độ xem trong chế độ xem thay vì mô hình chế độ xem?

Thứ hai, có vẻ như thiết kế không chuyển đổi của bạn cố gắng sửa đổi các thuộc tính đối tượng thực tế đã tồn tại. Có vẻ như họ đã triển khai INotifyPropertyChanged, vậy tại sao không sử dụng tạo một đối tượng trình bao bọc dành riêng cho chế độ xem để liên kết? Đây là một ví dụ đơn giản:

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

Tôi không có ý ám chỉ rằng tôi đang thay đổi các thuộc tính của mô hình thực thể của tôi trực tiếp trong chế độ xem hoặc mô hình xem. Mô hình khung nhìn chắc chắn là một lớp khác với lớp mô hình thực thể của tôi. Trên thực tế, công việc tôi đã thực hiện cho đến khi ở chế độ chỉ đọc. Điều này không có nghĩa là ứng dụng của tôi sẽ không liên quan đến bất kỳ chỉnh sửa nào, nhưng tôi không thấy bất kỳ chuyển đổi nào được thực hiện trên các điều khiển được sử dụng để chỉnh sửa (vì vậy giả sử tất cả các ràng buộc là một chiều, ngoại trừ các lựa chọn trong danh sách). Điểm tốt về "xem dữ liệu" mặc dù. Đó là một khái niệm được nêu ra trong bài đăng của các môn đệ XAML mà tôi đã đề cập ở đầu câu hỏi của tôi.
devuxer

0

Đôi khi nó là tốt để sử dụng một công cụ chuyển đổi giá trị để tận dụng lợi thế của ảo hóa.

Một ví dụ về điều này trong một dự án nơi chúng tôi phải hiển thị dữ liệu bitmasked cho hàng trăm ngàn ô trong một lưới. Khi chúng tôi giải mã bitmasks trong mô hình khung nhìn cho mỗi ô, chương trình mất quá nhiều thời gian để tải.

Nhưng khi chúng tôi tạo một trình chuyển đổi giá trị giải mã một ô duy nhất, chương trình được tải trong một phần nhỏ thời gian và phản hồi nhanh vì trình chuyển đổi chỉ được gọi khi người dùng đang nhìn vào một ô cụ thể (và nó chỉ cần được gọi tối đa ba mươi lần bất cứ khi nào người dùng thay đổi quan điểm của họ trên lưới).

Tôi không biết giải pháp khiếu nại MVVM như thế nào, nhưng nó đã cắt giảm 95% thời gian tải.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.