ฉันกำลังทำงานกับแอปพลิเคชัน WPF ที่มีมุมมองที่ต้องการการแปลงค่าจำนวนมาก ในตอนแรกปรัชญาของฉัน (ได้รับแรงบันดาลใจจากการอภิปรายที่มีชีวิตชีวาใน XAML Disciples ) คือฉันควรสร้างแบบจำลองมุมมองอย่างเคร่งครัดเกี่ยวกับการสนับสนุนข้อกำหนดด้านข้อมูลของมุมมอง ซึ่งหมายความว่าการแปลงค่าใด ๆ ที่จำเป็นในการแปลงข้อมูลเป็นสิ่งที่มองเห็นได้แปรงขนาด ฯลฯ จะได้รับการจัดการกับตัวแปลงค่าและตัวแปลงค่าหลายค่า แนวคิดนี้ดูเหมือนจะค่อนข้างหรูหรา รูปแบบการดูและมุมมองทั้งสองจะมีจุดประสงค์ที่แตกต่างกันและแยกกันอย่างชัดเจน เส้นที่ชัดเจนจะถูกวาดระหว่าง "data" และ "look"
หลังจากให้กลยุทธ์นี้ "ลองวิทยาลัยเก่า" ฉันมีข้อสงสัยว่าฉันต้องการพัฒนาวิธีนี้ต่อไปหรือไม่ ฉันกำลังพิจารณาการทิ้งตัวแปลงค่าและวางความรับผิดชอบสำหรับ (เกือบ) การแปลงค่าทั้งหมดให้อยู่ในมือของรูปแบบการดูอย่างจริงจัง
ความเป็นจริงของการใช้ตัวแปลงมูลค่านั้นดูเหมือนจะไม่สามารถวัดได้ถึงมูลค่าที่ชัดเจนของข้อกังวลที่แยกออกจากกันอย่างหมดจด ปัญหาที่ใหญ่ที่สุดของฉันเกี่ยวกับตัวแปลงมูลค่าคือพวกเขาน่าเบื่อที่จะใช้ คุณต้องสร้างคลาสใหม่ใช้งานIValueConverter
หรือIMultiValueConverter
โยนค่าหรือค่าจากobject
ประเภทที่ถูกต้องทดสอบสำหรับDependencyProperty.Unset
(อย่างน้อยสำหรับตัวแปลงหลายค่า) เขียนตรรกะการแปลงลงทะเบียนตัวแปลงในพจนานุกรมทรัพยากร [ดูการปรับปรุงด้านล่าง ] และสุดท้ายเชื่อมต่อตัวแปลงโดยใช้ XAML ค่อนข้างละเอียด (ซึ่งจำเป็นต้องใช้สายอักขระเวทสำหรับทั้งการผูกและชื่อของตัวแปลง[ดูอัปเดตด้านล่าง]) กระบวนการตรวจแก้จุดบกพร่องนั้นไม่มีการปิคนิคเนื่องจากข้อความแสดงข้อผิดพลาดมักจะเป็นความลับโดยเฉพาะในโหมดการออกแบบ / Expression Blend ของ Visual Studio
นี่ไม่ได้หมายความว่าทางเลือก - การสร้างตัวแบบมุมมองที่รับผิดชอบต่อการแปลงค่าทั้งหมด - เป็นการปรับปรุง นี่อาจเป็นเรื่องของหญ้าที่เป็นสีเขียวในอีกด้านหนึ่ง นอกจากการสูญเสียความกังวลที่แยกจากกันอย่างสง่างามแล้วคุณต้องเขียนคุณสมบัติที่ได้รับจำนวนมากและทำให้แน่ใจว่าคุณโทรไปอย่างจริงใจRaisePropertyChanged(() => DerivedProperty)
เมื่อตั้งค่าคุณสมบัติพื้นฐานซึ่งอาจพิสูจน์ได้ว่าเป็นปัญหาการบำรุงรักษาที่ไม่พึงประสงค์
ต่อไปนี้เป็นรายการเริ่มต้นที่ฉันรวบรวมข้อดีข้อเสียของการอนุญาตให้แบบจำลองมุมมองจัดการตรรกะการแปลงและทำไปกับตัวแปลงค่า:
- ข้อดี:
- การผูกรวมที่น้อยลงเนื่องจากตัวแปลงหลายตัวถูกกำจัด
- ลดจำนวนสตริงเวทมนตร์ (พา ธ การโยง
+ ชื่อรีซอร์สตัวแปลง) ไม่มีการลงทะเบียนแต่ละตัวแปลงอีกต่อไป (รวมทั้งการบำรุงรักษารายการนี้)- ทำงานน้อยลงในการเขียนตัวแปลงแต่ละตัว (ไม่จำเป็นต้องใช้อินเทอร์เฟซหรือการคัดเลือกนักแสดง)
- สามารถฉีดการพึ่งพาเพื่อช่วยในการแปลงได้อย่างง่ายดาย (เช่นตารางสี)
- มาร์กอัป XAML นั้นละเอียดน้อยกว่าและอ่านง่ายกว่า
- ยังสามารถใช้ซ้ำตัวแปลงได้ (แม้ว่าต้องมีการวางแผนบางอย่าง)
- ไม่มีปัญหาลึกลับกับ DependencyProperty.Unset (ปัญหาที่ฉันสังเกตเห็นด้วยตัวแปลงหลายค่า)
* Strikethroughs ระบุถึงประโยชน์ที่จะหายไปหากคุณใช้ส่วนขยายมาร์กอัป (ดูอัปเดตด้านล่าง)
- จุดด้อย:
- การมีเพศสัมพันธ์ที่ดีขึ้นระหว่างโมเดลมุมมองและมุมมอง (เช่นคุณสมบัติต้องจัดการกับแนวคิดเช่นการมองเห็นและแปรง)
- คุณสมบัติทั้งหมดเพิ่มเติมเพื่ออนุญาตการแมปโดยตรงสำหรับทุกการเชื่อมโยงในมุมมอง
(ดูอัปเดต 2 ด้านล่าง)RaisePropertyChanged
ต้องถูกเรียกสำหรับแต่ละคุณสมบัติที่ได้รับ- จะต้องยังคงใช้ตัวแปลงหากการแปลงขึ้นอยู่กับคุณสมบัติขององค์ประกอบ UI
อย่างที่คุณบอกได้ฉันมีอาการเสียดท้องกับเรื่องนี้ ฉันลังเลมากที่จะลงไปสู่ถนนของการปรับสภาพใหม่เท่านั้นที่จะรู้ว่ากระบวนการเข้ารหัสนั้นไม่มีประสิทธิภาพและน่าเบื่อไม่ว่าฉันจะใช้ตัวแปลงค่าหรือเปิดเผยคุณสมบัติการแปลงค่าจำนวนมากในแบบจำลองมุมมองของฉัน
ฉันไม่มีข้อดีข้อเสียหรือไม่? สำหรับผู้ที่ลองใช้การแปลงค่าทั้งสองแบบคุณได้ผลดีกว่าสำหรับคุณและทำไม มีทางเลือกอื่นอีกไหม? (สานุศิษย์พูดถึงบางอย่างเกี่ยวกับผู้ให้บริการตัวอธิบายประเภท แต่ฉันไม่สามารถจัดการกับสิ่งที่พวกเขาพูดถึงความเข้าใจในเรื่องนี้จะได้รับการชื่นชม)
ปรับปรุง
ฉันพบว่าวันนี้เป็นไปได้ที่จะใช้สิ่งที่เรียกว่า "ส่วนขยายมาร์กอัป" เพื่อกำจัดความจำเป็นในการลงทะเบียนตัวแปลงค่า ในความเป็นจริงมันไม่เพียง แต่จะช่วยลดความจำเป็นที่จะต้องลงทะเบียนกับพวกเขา แต่มันเป็นจริงให้ IntelliSense Converter=
สำหรับการเลือกแปลงเมื่อคุณพิมพ์ นี่คือบทความที่มีฉันเริ่มต้น: http://www.wpftutorial.net/ValueConverters.html
ความสามารถในการใช้ส่วนขยายมาร์กอัปจะเปลี่ยนความสมดุลในรายการข้อดีและข้อเสียและการอภิปรายด้านบนของฉัน (ดูขีดฆ่า)
จากการเปิดเผยนี้ฉันกำลังทดลองกับระบบไฮบริดที่ฉันใช้ตัวแปลงBoolToVisibility
และสิ่งที่ฉันโทรMatchToVisibility
และรูปแบบการดูสำหรับการแปลงอื่น ๆ ทั้งหมด MatchToVisibility นั้นเป็นตัวแปลงที่ให้ฉันตรวจสอบว่าค่าที่ถูกผูกไว้ (ปกติคือ enum) ตรงกับค่าหนึ่งค่าหรือมากกว่าที่ระบุใน XAML
ตัวอย่าง:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
โดยทั่วไปสิ่งนี้จะตรวจสอบว่าสถานะเป็นเสร็จแล้วหรือยกเลิก ถ้าเป็นเช่นนั้นการมองเห็นจะถูกตั้งค่าเป็น "มองเห็นได้" มิฉะนั้นจะถูกตั้งค่าเป็น "ซ่อน" สิ่งนี้กลายเป็นสถานการณ์ที่พบได้บ่อยมากและการมีตัวแปลงนี้ช่วยฉันเกี่ยวกับคุณสมบัติ 15 รายการในโมเดลมุมมองของฉัน (รวมถึงคำสั่ง RaisePropertyChanged ที่เกี่ยวข้อง) โปรดทราบว่าเมื่อคุณพิมพ์Converter={vc:
"MatchToVisibility" จะปรากฏในเมนู Intellisense สิ่งนี้จะช่วยลดโอกาสในการเกิดข้อผิดพลาดและทำให้การใช้ตัวแปลงค่าน่าเบื่อน้อยลง (คุณไม่จำเป็นต้องจำหรือค้นหาชื่อของตัวแปลงค่าที่คุณต้องการ)
ในกรณีที่คุณอยากรู้อยากเห็นฉันจะวางรหัสด้านล่าง หนึ่งคุณลักษณะที่สำคัญของการดำเนินการนี้MatchToVisibility
ก็คือว่ามันจะตรวจสอบเพื่อดูว่าค่าขอบเขตเป็นenum
และถ้ามันคือมันตรวจสอบเพื่อให้แน่ใจว่าValue1
, Value2
ฯลฯ นอกจากนี้ยังมี enums ประเภทเดียวกัน สิ่งนี้จัดเตรียมการตรวจสอบขณะออกแบบและรันไทม์ว่าค่า enum ใด ๆ มีการพิมพ์ผิดหรือไม่ เพื่อปรับปรุงให้เป็นการตรวจสอบเวลาแบบคอมไพล์คุณสามารถใช้สิ่งต่อไปนี้แทน (ฉันพิมพ์ด้วยมือดังนั้นโปรดยกโทษให้ฉันถ้าฉันทำผิดพลาด):
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}}"
แม้ว่ามันจะปลอดภัยกว่า แต่ก็เป็นเพียง verbose ที่คุ้มค่าสำหรับฉัน ฉันอาจใช้คุณสมบัติในโมเดลมุมมองก็ได้เช่นกันถ้าฉันจะทำเช่นนี้ อย่างไรก็ตามฉันพบว่าการตรวจสอบเวลาออกแบบเพียงพออย่างยิ่งสำหรับสถานการณ์ที่ฉันได้ลองมาแล้ว
นี่คือรหัสสำหรับ 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;
}
}
นี่คือรหัสสำหรับ 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();
}
}
นี่คือวิธีการขยาย ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
อัปเดต 2
ตั้งแต่ฉันโพสต์คำถามนี้ฉันได้เจอโครงการโอเพนซอร์ซที่ใช้ "การทอผ้า IL" เพื่อฉีดรหัส NotifyPropertyChanged สำหรับคุณสมบัติและคุณสมบัติที่ต้องพึ่งพา สิ่งนี้ทำให้การใช้วิสัยทัศน์ของ Josh Smith เกี่ยวกับโมเดลมุมมองเป็น "ตัวแปลงค่าบนเตียรอยด์" เป็นเรื่องง่าย คุณสามารถใช้ "คุณสมบัตินำมาใช้โดยอัตโนมัติ" และผู้ประกอบจะทำหน้าที่ส่วนที่เหลือ
ตัวอย่าง:
หากฉันป้อนรหัสนี้:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... นี่คือสิ่งที่รวบรวมได้:
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);
}
}
นั่นคือการประหยัดจำนวนมหาศาลของรหัสที่คุณต้องพิมพ์อ่านเลื่อนผ่านไปและที่สำคัญกว่านั้นคือมันช่วยให้คุณไม่ต้องคิดว่าการพึ่งพาของคุณคืออะไร คุณสามารถเพิ่ม "คุณสมบัติ" แบบใหม่FullName
โดยไม่ต้องไปถึงห่วงโซ่การพึ่งพาเพื่อเพิ่มการRaisePropertyChanged()
โทร
โครงการโอเพนซอร์ซนี้เรียกว่าอะไร? รุ่นเดิมเรียกว่า "NotifyPropertyWeaver" แต่เจ้าของ (Simon Potter) ได้สร้างแพลตฟอร์มที่เรียกว่า "Fody" สำหรับโฮสต์ชุดทอผ้า IL ทั้งหมด NotifyPropertyWeaver ที่เทียบเท่าในแพลตฟอร์มใหม่นี้เรียกว่า PropertyChanged.Fody
- คำแนะนำในการตั้งค่า Fody: http://code.google.com/p/fody/wiki/SampleUsage (แทนที่ "Virtuosity" ด้วย "PropertyChanged")
- เว็บไซต์โครงการ PropertyChanged.Fody: http://code.google.com/p/propertychanged/
หากคุณต้องการใช้ NotifyPropertyWeaver (ซึ่งติดตั้งได้ง่ายกว่าเล็กน้อย แต่ไม่จำเป็นต้องได้รับการอัปเดตในอนาคตนอกเหนือจากการแก้ไขข้อบกพร่อง) นี่คือไซต์โครงการ: http://code.google.com/p/ notifypropertyweaver /
ไม่ว่าจะด้วยวิธีใดโซลูชั่น IL ผู้ประกอบการเหล่านี้จะเปลี่ยนแคลคูลัสอย่างสมบูรณ์ในการอภิปรายระหว่างมุมมองแบบจำลองบนสเตอรอยด์กับตัวแปลงค่า
MatchToVisibility
ดูเหมือนจะเป็นวิธีที่สะดวกในการเปิดใช้งานสวิตช์โหมดแบบง่าย ๆ (ฉันมีมุมมองหนึ่งโดยเฉพาะที่มีส่วนต่างๆมากมายที่สามารถเปิดและปิดได้ในกรณีส่วนใหญ่มุมมองจะมีป้ายกำกับ (พร้อมx:Name
) ให้ตรงกับโหมด พวกเขาสอดคล้องกับ.) มันไม่ได้เกิดขึ้นกับฉันจริงๆว่านี่คือ "ตรรกะทางธุรกิจ" แต่ฉันจะให้ความคิดเห็นของคุณบางความคิด
BooleanToVisibility
ใช้ค่าหนึ่งที่เกี่ยวข้องกับการมองเห็น (จริง / เท็จ) และแปลเป็นค่าอื่นValueConverter
นี้ดูเหมือนว่าการใช้งานที่เหมาะของ ในอีกทางหนึ่งMatchToVisibility
คือการเข้ารหัสตรรกะทางธุรกิจในView
(รายการประเภทใดที่ควรมองเห็น) ในความเห็นของฉันตรรกะนี้ควรถูกผลักลงไปViewModel
หรือยิ่งกว่านั้นในสิ่งที่ฉันเรียกEditModel
ว่า สิ่งที่ผู้ใช้สามารถเห็นควรมีอะไรบางอย่างภายใต้การทดสอบ