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 IValueConverter
hoặ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
(xem Cập nhật 2 bên dưới)RaisePropertyChanged
phải được gọi cho mỗi thuộc tính dẫn xuất- 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 BoolToVisibility
và những gì tôi gọi MatchToVisibility
và 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 MatchToVisibility
là 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
, Value2
vv 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ế FullName
mà 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.
- Hướng dẫn thiết lập Fody: http://code.google.com.vn/p/fody/wiki/SampleUsage (thay thế "Virtuosity" bằng "PropertyChanged")
- Trang web của dự án PropertyChanged.Fody: http://code.google.com.vn/p/propertychanged/
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ị.
MatchToVisibility
dườ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.
BooleanToVisibility
lấ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 aValueConverter
. Mặt khác,MatchToVisibility
mã hóa logic nghiệp vụ trongView
(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ốngViewModel
, 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.