ตัวแปลงมูลค่ามีปัญหามากกว่าที่พวกเขาคุ้มค่าหรือไม่


20

ฉันกำลังทำงานกับแอปพลิเคชัน WPF ที่มีมุมมองที่ต้องการการแปลงค่าจำนวนมาก ในตอนแรกปรัชญาของฉัน (ได้รับแรงบันดาลใจจากการอภิปรายที่มีชีวิตชีวาใน XAML Disciples ) คือฉันควรสร้างแบบจำลองมุมมองอย่างเคร่งครัดเกี่ยวกับการสนับสนุนข้อกำหนดด้านข้อมูลของมุมมอง ซึ่งหมายความว่าการแปลงค่าใด ๆ ที่จำเป็นในการแปลงข้อมูลเป็นสิ่งที่มองเห็นได้แปรงขนาด ฯลฯ จะได้รับการจัดการกับตัวแปลงค่าและตัวแปลงค่าหลายค่า แนวคิดนี้ดูเหมือนจะค่อนข้างหรูหรา รูปแบบการดูและมุมมองทั้งสองจะมีจุดประสงค์ที่แตกต่างกันและแยกกันอย่างชัดเจน เส้นที่ชัดเจนจะถูกวาดระหว่าง "data" และ "look"

หลังจากให้กลยุทธ์นี้ "ลองวิทยาลัยเก่า" ฉันมีข้อสงสัยว่าฉันต้องการพัฒนาวิธีนี้ต่อไปหรือไม่ ฉันกำลังพิจารณาการทิ้งตัวแปลงค่าและวางความรับผิดชอบสำหรับ (เกือบ) การแปลงค่าทั้งหมดให้อยู่ในมือของรูปแบบการดูอย่างจริงจัง

ความเป็นจริงของการใช้ตัวแปลงมูลค่านั้นดูเหมือนจะไม่สามารถวัดได้ถึงมูลค่าที่ชัดเจนของข้อกังวลที่แยกออกจากกันอย่างหมดจด ปัญหาที่ใหญ่ที่สุดของฉันเกี่ยวกับตัวแปลงมูลค่าคือพวกเขาน่าเบื่อที่จะใช้ คุณต้องสร้างคลาสใหม่ใช้งานIValueConverterหรือIMultiValueConverterโยนค่าหรือค่าจากobjectประเภทที่ถูกต้องทดสอบสำหรับDependencyProperty.Unset(อย่างน้อยสำหรับตัวแปลงหลายค่า) เขียนตรรกะการแปลงลงทะเบียนตัวแปลงในพจนานุกรมทรัพยากร [ดูการปรับปรุงด้านล่าง ] และสุดท้ายเชื่อมต่อตัวแปลงโดยใช้ XAML ค่อนข้างละเอียด (ซึ่งจำเป็นต้องใช้สายอักขระเวทสำหรับทั้งการผูกและชื่อของตัวแปลง[ดูอัปเดตด้านล่าง]) กระบวนการตรวจแก้จุดบกพร่องนั้นไม่มีการปิคนิคเนื่องจากข้อความแสดงข้อผิดพลาดมักจะเป็นความลับโดยเฉพาะในโหมดการออกแบบ / Expression Blend ของ Visual Studio

นี่ไม่ได้หมายความว่าทางเลือก - การสร้างตัวแบบมุมมองที่รับผิดชอบต่อการแปลงค่าทั้งหมด - เป็นการปรับปรุง นี่อาจเป็นเรื่องของหญ้าที่เป็นสีเขียวในอีกด้านหนึ่ง นอกจากการสูญเสียความกังวลที่แยกจากกันอย่างสง่างามแล้วคุณต้องเขียนคุณสมบัติที่ได้รับจำนวนมากและทำให้แน่ใจว่าคุณโทรไปอย่างจริงใจRaisePropertyChanged(() => DerivedProperty)เมื่อตั้งค่าคุณสมบัติพื้นฐานซึ่งอาจพิสูจน์ได้ว่าเป็นปัญหาการบำรุงรักษาที่ไม่พึงประสงค์

ต่อไปนี้เป็นรายการเริ่มต้นที่ฉันรวบรวมข้อดีข้อเสียของการอนุญาตให้แบบจำลองมุมมองจัดการตรรกะการแปลงและทำไปกับตัวแปลงค่า:

  • ข้อดี:
    • การผูกรวมที่น้อยลงเนื่องจากตัวแปลงหลายตัวถูกกำจัด
    • ลดจำนวนสตริงเวทมนตร์ (พา ธ การโยง+ ชื่อรีซอร์สตัวแปลง)
    • ไม่มีการลงทะเบียนแต่ละตัวแปลงอีกต่อไป (รวมทั้งการบำรุงรักษารายการนี้)
    • ทำงานน้อยลงในการเขียนตัวแปลงแต่ละตัว (ไม่จำเป็นต้องใช้อินเทอร์เฟซหรือการคัดเลือกนักแสดง)
    • สามารถฉีดการพึ่งพาเพื่อช่วยในการแปลงได้อย่างง่ายดาย (เช่นตารางสี)
    • มาร์กอัป XAML นั้นละเอียดน้อยกว่าและอ่านง่ายกว่า
    • ยังสามารถใช้ซ้ำตัวแปลงได้ (แม้ว่าต้องมีการวางแผนบางอย่าง)
    • ไม่มีปัญหาลึกลับกับ DependencyProperty.Unset (ปัญหาที่ฉันสังเกตเห็นด้วยตัวแปลงหลายค่า)

* Strikethroughs ระบุถึงประโยชน์ที่จะหายไปหากคุณใช้ส่วนขยายมาร์กอัป (ดูอัปเดตด้านล่าง)

  • จุดด้อย:
    • การมีเพศสัมพันธ์ที่ดีขึ้นระหว่างโมเดลมุมมองและมุมมอง (เช่นคุณสมบัติต้องจัดการกับแนวคิดเช่นการมองเห็นและแปรง)
    • คุณสมบัติทั้งหมดเพิ่มเติมเพื่ออนุญาตการแมปโดยตรงสำหรับทุกการเชื่อมโยงในมุมมอง
    • RaisePropertyChangedต้องถูกเรียกสำหรับแต่ละคุณสมบัติที่ได้รับ (ดูอัปเดต 2 ด้านล่าง)
    • จะต้องยังคงใช้ตัวแปลงหากการแปลงขึ้นอยู่กับคุณสมบัติขององค์ประกอบ 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

หากคุณต้องการใช้ NotifyPropertyWeaver (ซึ่งติดตั้งได้ง่ายกว่าเล็กน้อย แต่ไม่จำเป็นต้องได้รับการอัปเดตในอนาคตนอกเหนือจากการแก้ไขข้อบกพร่อง) นี่คือไซต์โครงการ: http://code.google.com/p/ notifypropertyweaver /

ไม่ว่าจะด้วยวิธีใดโซลูชั่น IL ผู้ประกอบการเหล่านี้จะเปลี่ยนแคลคูลัสอย่างสมบูรณ์ในการอภิปรายระหว่างมุมมองแบบจำลองบนสเตอรอยด์กับตัวแปลงค่า


เพียงแค่ทราบว่า: BooleanToVisibilityใช้ค่าหนึ่งที่เกี่ยวข้องกับการมองเห็น (จริง / เท็จ) และแปลเป็นค่าอื่น ValueConverterนี้ดูเหมือนว่าการใช้งานที่เหมาะของ ในอีกทางหนึ่งMatchToVisibilityคือการเข้ารหัสตรรกะทางธุรกิจในView(รายการประเภทใดที่ควรมองเห็น) ในความเห็นของฉันตรรกะนี้ควรถูกผลักลงไปViewModelหรือยิ่งกว่านั้นในสิ่งที่ฉันเรียกEditModelว่า สิ่งที่ผู้ใช้สามารถเห็นควรมีอะไรบางอย่างภายใต้การทดสอบ
Scott Whitlock

@Scott นั่นเป็นจุดที่ดี แอพที่ฉันทำงานอยู่ตอนนี้ไม่ใช่แอพ "ธุรกิจ" จริง ๆ ที่มีระดับการอนุญาตที่แตกต่างกันสำหรับผู้ใช้ดังนั้นฉันจึงไม่ได้คิดตามบรรทัดเหล่านั้น MatchToVisibilityดูเหมือนจะเป็นวิธีที่สะดวกในการเปิดใช้งานสวิตช์โหมดแบบง่าย ๆ (ฉันมีมุมมองหนึ่งโดยเฉพาะที่มีส่วนต่างๆมากมายที่สามารถเปิดและปิดได้ในกรณีส่วนใหญ่มุมมองจะมีป้ายกำกับ (พร้อมx:Name) ให้ตรงกับโหมด พวกเขาสอดคล้องกับ.) มันไม่ได้เกิดขึ้นกับฉันจริงๆว่านี่คือ "ตรรกะทางธุรกิจ" แต่ฉันจะให้ความคิดเห็นของคุณบางความคิด
devuxer

ตัวอย่าง: สมมติว่าคุณมีระบบสเตอริโอที่อาจอยู่ในโหมดวิทยุซีดีหรือ MP3 สมมติว่ามีภาพที่สอดคล้องกับแต่ละโหมดในส่วนต่าง ๆ ของ UI คุณสามารถ (1) ให้มุมมองเลือกกราฟิกที่สอดคล้องกับโหมดใดและเปิด / ปิดตามนั้น (2) แสดงคุณสมบัติของโมเดลมุมมองสำหรับแต่ละโหมดค่า (เช่น IsModeRadio, IsModeCD) หรือ (3) เปิดเผย คุณสมบัติบนโมเดลมุมมองสำหรับองค์ประกอบ / กลุ่มกราฟิกแต่ละรายการ (เช่น IsRadioLightOn, IsCDButtonGroupOn) (1) ดูเป็นธรรมชาติสำหรับฉันเนื่องจากมันมีโหมดการรับรู้อยู่แล้ว คุณคิดอย่างไรกับกรณีนี้
devuxer

นี่เป็นคำถามที่ยาวที่สุดที่ฉันเคยเห็นใน SE ทั้งหมด! :]
trejder

คำตอบ:


10

ฉันได้ใช้ValueConvertersในบางกรณีและวางตรรกะในที่ViewModelอื่น ๆ ความรู้สึกของฉันคือการValueConverterเป็นส่วนหนึ่งของViewเลเยอร์ดังนั้นหากตรรกะเป็นส่วนหนึ่งของViewมันViewModelจริงๆ

ส่วนตัวผมไม่เห็นมีปัญหากับที่ViewModelจัดการกับViewแนวคิด -specific ชอบBrushES เพราะในการใช้งานของฉันViewModelเท่านั้นที่มีอยู่เป็นพื้นผิวที่ทดสอบและ bindable Viewสำหรับ อย่างไรก็ตามบางคนใส่ตรรกะทางธุรกิจจำนวนมากลงในViewModel(ฉันไม่ได้) และในกรณีนั้นViewModelมันก็เป็นเหมือนส่วนหนึ่งของชั้นธุรกิจของพวกเขาดังนั้นในกรณีนั้นฉันไม่ต้องการให้มี WPF เฉพาะสิ่งในนั้น

ฉันชอบความแตกต่าง:

  • View- สิ่งต่าง ๆ ของ WPF ซึ่งบางครั้งไม่สามารถทดสอบได้ (เช่น XAML และ code-behind) แต่ก็ValueConverterเป็นเช่นนั้น
  • ViewModel - คลาสที่ทดสอบได้และ bindable ที่เป็น WPF เฉพาะ
  • EditModel - ส่วนหนึ่งของชั้นธุรกิจที่แสดงถึงแบบจำลองของฉันในระหว่างการจัดการ
  • EntityModel - ส่วนหนึ่งของชั้นธุรกิจที่แสดงถึงแบบจำลองของฉันเป็นแบบคงที่
  • Repository- รับผิดชอบในการคงอยู่ของEntityModelฐานข้อมูล

ดังนั้นวิธีที่ฉันทำฉันมีประโยชน์เล็กน้อยสำหรับValueConverters

วิธีที่ฉันได้รับจาก "คอนแวนต์" ของคุณคือการทำให้ฉันViewModelเป็นคนธรรมดามาก ตัวอย่างเช่นสิ่งที่ViewModelฉันมีเรียกว่าChangeValueViewModelใช้คุณสมบัติป้ายกำกับและคุณสมบัติค่า ในการViewมีการLabelผูกกับคุณสมบัติป้ายชื่อและTextBoxที่ผูกกับคุณสมบัติค่า

ฉันมีแล้วChangeValueViewซึ่งเป็นDataTemplateคีย์ปิดChangeValueViewModelประเภท เมื่อใดก็ตามที่ WPF เห็นว่ามันใช้ว่าViewModel Viewตัวสร้างของฉันChangeValueViewModelต้องใช้ตรรกะปฏิสัมพันธ์จะต้องมีการฟื้นฟูของรัฐจากEditModel(มักจะเพียงแค่ผ่านในFunc<string>) และการกระทำที่เป็นความต้องการที่จะใช้เมื่อผู้ใช้แก้ไขมูลค่า (เพียงActionที่รันตรรกะบางอย่างในEditModel)

ผู้ปกครองViewModel(สำหรับหน้าจอ) ใช้เวลาEditModelในตัวสร้างและเพียงแค่ยกตัวอย่างประถมเหมาะสมViewModels ChangeValueViewModelเช่น เนื่องจากผู้ปกครองViewModelกำลังอัดฉีดการกระทำที่จะทำเมื่อผู้ใช้ทำการเปลี่ยนแปลงใด ๆ จึงสามารถขัดขวางการกระทำเหล่านี้ทั้งหมดและดำเนินการอื่น ๆ ดังนั้นการดำเนินการแก้ไขแบบฉีดสำหรับChangeValueViewModelอาจมีลักษณะดังนี้:

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

เห็นได้ชัดว่าforeachวงสามารถ refactored ที่อื่น แต่สิ่งนี้จะดำเนินการใช้กับรูปแบบแล้ว (สมมติว่ารูปแบบมีการปรับปรุงสถานะในทางที่ไม่รู้จัก) บอกเด็กทุกคนViewModelที่จะไปและรับสถานะของพวกเขาจาก รูปแบบอีกครั้ง หากรัฐมีการเปลี่ยนแปลงพวกเขามีความรับผิดชอบในการดำเนินPropertyChangedกิจกรรมตามความจำเป็น

ที่จัดการการโต้ตอบระหว่างพูดกล่องรายการและแผงรายละเอียดค่อนข้างดี เมื่อผู้ใช้เลือกตัวเลือกใหม่ผู้ใช้จะอัพเดตEditModelพร้อมกับตัวเลือกและEditModelเปลี่ยนค่าของคุณสมบัติที่เปิดเผยสำหรับแผงรายละเอียด ViewModelเด็กที่มีความรับผิดชอบสำหรับการแสดงข้อมูลรายละเอียดแผงโดยอัตโนมัติได้รับแจ้งว่าพวกเขาต้องตรวจสอบค่าใหม่และถ้าพวกเขาได้เปลี่ยนพวกเขายิงของพวกเขาPropertyChangedเหตุการณ์


/ พยักหน้า มันคล้ายกับที่ฉันมอง
เอียน

+1 ขอบคุณสำหรับคำตอบของคุณสกอตต์ฉันมีเลเยอร์เหมือนกันกับที่คุณทำและฉันก็ไม่ได้ใส่ตรรกะทางธุรกิจลงในโมเดลการดู (สำหรับบันทึกฉันใช้รหัส EntityFramework ก่อนและฉันมีเลเยอร์บริการที่แปลระหว่างมุมมองโมเดลและเอนทิตีเอนทิตีและในทางกลับกัน) ดังนั้นเมื่อพิจารณาจากนี้ฉันคิดว่าคุณกำลังพูดว่ามีค่าใช้จ่ายไม่มาก เพื่อวางตรรกะการแปลงทั้งหมด / เกือบทั้งหมดในเลเยอร์โมเดลมุมมอง
devuxer

@DanM - ใช่ฉันเห็นด้วย ฉันต้องการแปลงเป็นViewModelเลเยอร์ ไม่ใช่ทุกคนที่เห็นด้วยกับฉัน แต่ขึ้นอยู่กับการทำงานของสถาปัตยกรรมของคุณ
Scott Whitlock

2
ฉันกำลังจะบอกว่า +1 หลังจากอ่านย่อหน้าแรก แต่แล้วฉันอ่านอันที่สองของคุณและไม่เห็นด้วยอย่างยิ่งกับการใส่รหัสเฉพาะของการดูใน ViewModels ข้อยกเว้นเดียวคือถ้า ViewModel ถูกสร้างขึ้นโดยเฉพาะสำหรับการดูเบื้องหลังทั่วไป (เช่นCalendarViewModelสำหรับCalendarViewUserControl หรือDialogViewModelสำหรับ a DialogView) นั่นเป็นเพียงความคิดของฉันแม้ว่า :)
ราเชล

@ ราเชล - ถ้าคุณอ่านต่อไปสองย่อหน้าของฉันคุณจะเห็นว่านั่นคือสิ่งที่ฉันกำลังทำอยู่ :) ไม่มีตรรกะทางธุรกิจในของฉันคือViewModels
Scott Whitlock

8

หากการแปลงเป็นสิ่งที่เกี่ยวข้องกับการดูเช่นการตัดสินใจการมองเห็นวัตถุการกำหนดภาพที่จะแสดงหรือการหาสีแปรงที่จะใช้ฉันจะใส่เครื่องมือแปลงไว้ในมุมมอง

หากเกี่ยวข้องกับธุรกิจเช่นการพิจารณาว่าควรปิดบังฟิลด์หรือไม่หรือผู้ใช้มีสิทธิ์ดำเนินการการแปลงจะเกิดขึ้นใน ViewModel ของฉัน

จากตัวอย่างของคุณผมคิดว่าคุณกำลังขาดหายไปชิ้นใหญ่ของ DataTriggersWPF: ดูเหมือนว่าคุณกำลังใช้ตัวแปลงเพื่อกำหนดค่าตามเงื่อนไข แต่ตัวแปลงควรใช้สำหรับการแปลงชนิดข้อมูลหนึ่งไปเป็นอีกประเภทหนึ่ง

ในตัวอย่างของคุณด้านบน

ตัวอย่าง: สมมติว่าคุณมีระบบสเตอริโอที่อาจอยู่ในโหมดวิทยุซีดีหรือ MP3 สมมติว่ามีภาพที่สอดคล้องกับแต่ละโหมดในส่วนต่าง ๆ ของ UI คุณสามารถ (1) ให้มุมมองเลือกกราฟิกที่สอดคล้องกับโหมดใดและเปิด / ปิดตามนั้น (2) แสดงคุณสมบัติของโมเดลมุมมองสำหรับแต่ละโหมดค่า (เช่น IsModeRadio, IsModeCD) หรือ (3) เปิดเผย คุณสมบัติบนโมเดลมุมมองสำหรับองค์ประกอบ / กลุ่มกราฟิกแต่ละรายการ (เช่น IsRadioLightOn, IsCDButtonGroupOn) (1) ดูเป็นธรรมชาติสำหรับฉันเนื่องจากมันมีโหมดการรับรู้อยู่แล้ว คุณคิดว่าอะไรในกรณีนี้

ฉันจะใช้เพื่อตรวจสอบว่าภาพที่จะแสดงไม่ได้DataTrigger Converterตัวแปลงใช้สำหรับแปลงชนิดข้อมูลหนึ่งไปเป็นอีกประเภทหนึ่งในขณะที่ตัวเรียกใช้เพื่อกำหนดคุณสมบัติบางอย่างตามค่า

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

ครั้งเดียวที่ฉันจะพิจารณาใช้ตัวแปลงนี้คือถ้าค่าที่ถูกผูกไว้มีข้อมูลภาพอยู่จริงและฉันจำเป็นต้องแปลงให้เป็นประเภทข้อมูลที่ UI สามารถเข้าใจได้ ตัวอย่างเช่นหากแหล่งข้อมูลมีคุณสมบัติที่เรียกว่าImageFilePathกว่าฉันจะพิจารณาการใช้ตัวแปลงเพื่อแปลงสตริงที่มีตำแหน่งไฟล์รูปภาพBitmapImageที่สามารถใช้เป็นแหล่งที่มาสำหรับรูปภาพของฉัน

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

ผลลัพธ์ที่ได้คือฉันมีเนมสเปซหนึ่งไลบรารีที่เต็มไปด้วยตัวแปลงทั่วไปซึ่งแปลงชนิดข้อมูลหนึ่งไปเป็นอีกประเภทหนึ่งและฉันไม่ค่อยได้รหัสตัวแปลงใหม่ มีบางครั้งที่ฉันต้องการให้ผู้แปลงสำหรับการแปลงที่เฉพาะเจาะจง แต่พวกเขาไม่บ่อยพอที่ฉันไม่เคยเขียนพวกเขา


+1 คุณยกระดับคะแนนที่ดีบางอย่าง ฉันเคยใช้ทริกเกอร์มาก่อน แต่ในกรณีของฉันฉันไม่ได้สลับแหล่งภาพ (ซึ่งเป็นคุณสมบัติ) ฉันจะสลับGridองค์ประกอบทั้งหมด ฉันพยายามทำสิ่งต่าง ๆ เช่นชุดแปรงสำหรับพื้นหน้า / พื้นหลัง / ลายเส้นตามข้อมูลในโมเดลมุมมองของฉันและชุดจานสีเฉพาะที่กำหนดไว้ในไฟล์ปรับแต่ง ฉันไม่แน่ใจว่านี่เป็นแบบที่ดีสำหรับทริกเกอร์หรือตัวแปลง ปัญหาเดียวที่ฉันมีอยู่ตอนนี้ด้วยการวางตรรกะการดูส่วนใหญ่ในโมเดลการดูคือการเชื่อมต่อการRaisePropertyChanged()โทรทั้งหมด
devuxer

@DanM ฉันจะทำสิ่งเหล่านั้นทั้งหมดในจริง ๆDataTriggerแม้กระทั่งเปลี่ยนองค์ประกอบของกริด ฉันมักจะวางContentControlเนื้อหาแบบไดนามิกของฉันและสลับContentTemplateในการเรียก ฉันมีตัวอย่างที่ลิงค์ต่อไปนี้หากคุณสนใจ (เลื่อนลงไปที่ส่วนที่มีส่วนหัวของUsing a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel

ฉันเคยใช้เทมเพลตข้อมูลและการควบคุมเนื้อหามาก่อน แต่ฉันไม่เคยต้องการทริกเกอร์เพราะฉันมีโมเดลมุมมองที่ไม่ซ้ำกันสำหรับแต่ละมุมมอง อย่างไรก็ตามเทคนิคของคุณสมเหตุสมผลดีและค่อนข้างหรูหรา แต่ก็ยังละเอียดมาก ด้วย MatchToVisibility ก็สามารถย่อให้สั้นลงนี้<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

ขึ้นอยู่กับว่าคุณกำลังทดสอบอะไร

ไม่มีการทดสอบ: intermix ดูรหัสด้วย ViewModel ที่ต้องการ (คุณสามารถปรับโครงสร้างใหม่ได้ในภายหลัง)
ทดสอบกับ ViewModel และ / หรือต่ำกว่า: ใช้ตัวแปลง
การทดสอบในเลเยอร์ Model และ / หรือต่ำกว่า: intermix ดูโค้ดที่มี ViewModel ตามต้องการ

ViewModel บทคัดย่อรุ่นสำหรับดู โดยส่วนตัวฉันจะใช้ ViewModel สำหรับ Brush เป็นต้นและข้ามตัวแปลง ทดสอบเลเยอร์ที่ข้อมูลอยู่ในรูปแบบ " บริสุทธิ์ " (เช่นเลเยอร์โมเดล )


2
ประเด็นที่น่าสนใจเกี่ยวกับการทดสอบ แต่ฉันคิดว่าฉันไม่เห็นว่าการใช้ตรรกะตัวแปลงในรูปแบบมุมมองเป็นอันตรายต่อความสามารถในการทดสอบของโมเดลมุมมองอย่างไร ฉันไม่แนะนำให้ใส่การควบคุม UI จริงในโมเดลมุมมอง เพียงแค่ดูเฉพาะคุณสมบัติเช่นVisibility, และSolidColorBrush Thickness
devuxer

@DanM: ถ้าคุณกำลังใช้การดูครั้งแรกวิธีการแล้วไม่มีปัญหา อย่างไรก็ตามการใช้บางViewModel แรกวิธีการที่ ViewModel อ้างอิงดูก็อาจจะมีปัญหา
Jake Berger

สวัสดีเจย์เป็นวิธีการดูครั้งแรกแน่นอน มุมมองไม่รู้อะไรเกี่ยวกับโมเดลมุมมองยกเว้นชื่อของคุณสมบัติที่จำเป็นต้องเชื่อมโยง ขอบคุณที่ติดตาม +1
devuxer

0

นี่อาจจะไม่แก้ปัญหาทั้งหมดที่คุณกล่าวถึง แต่มีสองประเด็นที่ควรพิจารณา:

ก่อนอื่นคุณต้องวางโค้ดแปลงไว้ที่ใดที่หนึ่งในกลยุทธ์แรกของคุณ คุณพิจารณาส่วนหนึ่งของมุมมองหรือโมเดลมุมมองนั้นหรือไม่? หากเป็นส่วนหนึ่งของมุมมองทำไมไม่วางคุณสมบัติเฉพาะมุมมองในมุมมองแทนที่จะเป็นมุมมองโมเดล

ประการที่สองดูเหมือนว่าการออกแบบที่ไม่ใช่ตัวแปลงของคุณจะพยายามแก้ไขคุณสมบัติของวัตถุจริงที่มีอยู่แล้ว ดูเหมือนว่าพวกเขาใช้ INotifyPropertyChanged แล้วทำไมไม่ใช้สร้างวัตถุ wrapper เฉพาะมุมมองเพื่อผูกกับ? นี่คือตัวอย่างง่ายๆ:

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

ฉันไม่ได้หมายความว่าฉันกำลังเปลี่ยนคุณสมบัติของโมเดลเอนทิตีของฉันโดยตรงในมุมมองหรือมุมมองโมเดล โมเดลการดูเป็นเลเยอร์ที่แตกต่างจากเลเยอร์โมเดลเอนทิตีของฉันอย่างแน่นอน อันที่จริงงานที่ฉันได้ทำไปแล้วในมุมมองแบบอ่านอย่างเดียว นี่ไม่ได้หมายความว่าแอปพลิเคชันของฉันจะไม่เกี่ยวข้องกับการแก้ไขใด ๆ แต่ฉันไม่เห็นการแปลงใด ๆ ที่เกิดขึ้นกับตัวควบคุมที่ใช้สำหรับการแก้ไข (ดังนั้นการเชื่อมโยงทั้งหมดถือว่าเป็นทางเดียวยกเว้นการเลือกในรายการ) จุดที่ดีเกี่ยวกับ "มุมมองข้อมูล" ว่า นั่นเป็นแนวคิดที่เกิดขึ้นในโพสต์ของสาวก XAML ที่ฉันอ้างถึงที่ด้านบนของคำถามของฉัน
devuxer

0

บางครั้งมันเป็นการดีที่จะใช้ตัวแปลงค่าเพื่อใช้ประโยชน์จากการจำลองเสมือน

ตัวอย่างของสิ่งนี้ในโครงการที่เราต้องแสดงข้อมูล bitmasked สำหรับเซลล์นับแสนในตาราง เมื่อเราถอดรหัส bitmasks ในรูปแบบมุมมองสำหรับทุกเซลล์เดียวโปรแกรมใช้เวลาโหลดนานเกินไป

แต่เมื่อเราสร้างตัวแปลงค่าที่ถอดรหัสเซลล์เดียวโปรแกรมที่โหลดในเวลาไม่นานและตอบสนองได้ดีเพราะตัวแปลงจะถูกเรียกเมื่อผู้ใช้ดูเซลล์ใดเซลล์หนึ่งเท่านั้น (และจะต้องถูกเรียกเท่านั้น สูงสุดสามสิบครั้งทุกครั้งที่ผู้ใช้เปลี่ยนมุมมองของพวกเขาในตาราง)

ฉันไม่ทราบว่าวิธีการแก้ปัญหาของ MVVM นั้นเป็นอย่างไร แต่มันลดเวลาในการโหลดลง 95%

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.