2016 年 7 月

第 31 卷,第 7 期

本文章是由機器翻譯。

資料繫結 - 實作 .NET 中資料繫結更好的方法

標記 Sowul

資料繫結是功能強大的技術來開發使用者介面︰ 其不僅讓您更容易就能將檢視邏輯與商務邏輯分隔開來,測試所產生的程式碼也更為輕鬆。雖然存在於 Microsoft.NET Framework 開始之後,資料繫結也變得更重要的 Windows Presentation Foundation (WPF) 和 XAML,隨著如它的檢視和 ViewModel 之間形成 「 粘附 」 Model View ViewModel (MVVM) 模式中。

實作資料繫結的缺點一直都需要的神奇的字串與未定案程式碼,來廣播內容中的變更和繫結至它們的 UI 項目。多年來,各種工具和技巧有走降低痛苦。這篇文章的目標是為了簡化程序更進一步。

首先,我將探討實作資料繫結,以及常見的技術來簡化它的基本概念 (如果您已經熟悉主體,放心地略過這些章節)。接下來,我會開發的技術,您可能不會有考慮 (「 第三個方法 」),並介紹使用 MVVM 開發應用程式時,發現相關的設計問題的解決方案。您可以取得最終的版本隨附的程式碼下載中,在這裡開發的架構,或將 SolSoft.DataBinding NuGet 封裝加入您自己的專案。

基本概念: INotifyPropertyChanged

實作 INotifyPropertyChanged 是讓物件可繫結至 UI 的慣用的方法。相當簡單,其中包含只包含一個成員︰ PropertyChanged 事件。物件應該引發這個事件可繫結的屬性變更時,若要通知檢視,就應該重新整理屬性值的表示法。

介面很簡單,不僅實作它。以手動方式引發事件以硬式編碼的文字屬性名稱並非解決方案可靈活,也不會不它抵禦重構︰ 您必須格外謹慎以確保文字的名稱會保持與程式碼中的屬性名稱的同步處理。這將會不您可利用到您後續任務。以下為範例:

public int UnreadItemCount
{
  get
  {
    return m_unreadItemCount;
  }
  set
  {
    m_unreadItemCount = value;
    OnNotifyPropertyChanged(
      new PropertyChangedEventArgs("UnreadItemCount")); // Yuck
  }
}

有數個技術人員開發回應,以維護其健全狀態 (請參閱,比方說,堆疊溢位問題在 bit.ly/24ZQ7CY); 大部分都屬於下列其中一種。

常用的技巧 1: 基底類別

為了簡化這種情況之一,是與基底類別,以便重複使用部分的未定案邏輯。這也會提供數種方法,以程式設計的方式,而不必硬式編碼它取得的屬性名稱。

取得運算式的屬性名稱︰ .NET Framework 3.5 引進了運算式,可讓程式碼結構的執行階段檢查。LINQ 會使用此 API 來產生良好效果,比方說,將.NET LINQ 查詢轉譯成 SQL 陳述式。Enterprising 開發人員也必須利用此 API 來檢查屬性名稱。為了執行此檢查使用的基底類別,上述的 setter 可以改寫為︰

public int UnreadItemCount
...
set
{
  m_unreadItemCount = value;
  RaiseNotifyPropertyChanged(() => UnreadItemCount);
}

如此一來,重新命名 UnreadItemCount 也要重新命名的運算式參考,讓程式碼仍然能夠運作。RaiseNotifyPropertyChanged 的簽章應如下所示︰

void RaiseNotifyPropertyChanged<T>(Expression<Func<T>> memberExpression)

有各種技術可從 memberExpression 擷取的屬性名稱。在 C# MSDN 部落格 bit.ly/25baMHM 提供一個簡單的範例︰

public static string GetName<T>(Expression<Func<T>> e)
{
  var member = (MemberExpression)e.Body;
  return member.Member.Name;
}

StackOverflow 提供更全面的列出項目在 bit.ly/23Xczu2。在任何情況下,為這項技巧的缺點︰ 擷取運算式的名稱會使用反映和反映變慢。可顯著多少屬性變更的通知有根據的效能負擔。

正在擷取 CallerMemberName 屬性名稱︰ C# 5.0 和.NET Framework 4.5 連線以其他方式來擷取屬性名稱,使用 (您可以使用此與舊版本的.NET Framework 透過 nuget Microsoft.Bcl),CallerMemberName 屬性。這次的編譯器執行的工作,因此沒有執行階段額外負荷。使用這個方法,此方法會變成︰

void RaiseNotifyPropertyChanged<T>([CallerMemberName] string propertyName = "")
And the call to it is:
public int UnreadItemCount
...
set
{
  m_unreadItemCount = value;
  RaiseNotifyPropertyChanged();
}

屬性會指示編譯器將呼叫者的名稱,UnreadItemCount,做為選擇性參數 propertyName 值填入。

正在擷取 nameof 屬性名稱︰ (基底類別中引發 PropertyChanged) 此使用案例的可能量身訂做,CallerMemberName 屬性卻在 C# 6 中,編譯器小組最後提供一些更廣泛地有用的︰ nameof 關鍵字。Nameof 很方便用於許多用途。在此情況下,如果我以運算式為基礎的程式碼取代 nameof 時,請再一次編譯器會執行所有工作 (沒有執行階段額外負荷)。值得注意的是這完全編譯器的版本功能而非.NET 版本的功能︰ 您可以使用這項技術,仍然繼續目標.NET Framework 2.0。不過,您 (和所有小組成員) 需要一定使用最少的 Visual Studio 2015。使用 nameof 看起來像這樣︰

public int UnreadItemCount
...
set
{
  m_unreadItemCount = value;
  RaiseNotifyPropertyChanged(nameof(UnreadItemCount));
}

有是一般性的問題,不過,使用任何基底類別方法︰ 它 「 燒壞基底類別,」 此一說到。如果您想要擴充不同類別檢視模型,您就是沒輒了。它也不做任何動作來處理 「 相依 」 屬性 (例如,串連 FirstName 和 LastName 的 FullName 屬性︰ 名字或姓氏的任何變更也必須要觸發 FullName 變更)。

常用的技巧 2: 層面導向程式設計

外觀導向程式設計 (AOP) 是一種技術,基本上"後置處理 「 編譯的程式碼,在執行階段或編譯後的步驟中,以便新增 (又稱為 「 外觀 」) 的特定行為。通常,目的是要取代重複的未定案程式碼,例如記錄或例外狀況處理 (所謂 「 跨領域問題 」)。不用多說,實作 INotifyPropertyChanged 是很好的候選。

有幾個工具組適用於這種方法。PostSharp 是其中一個 (bit.ly/1Xmq4n2)。我感到非常驚喜了正確處理的相依屬性 (例如,FullName 屬性稍早所述)。開放原始碼架構,稱為 「 Fody 」 很類似 (bit.ly/1wXR2VA)。

這是一種吸引人的方法。其缺點可能不多。某些實作攔截行為在執行階段,會產生效能成本。編譯後架構相較之下,不應該會產生任何執行階段額外負荷,但是可能需要某種形式的安裝或組態。PostSharp 目前提供做為擴充 Visual Studio。其可用的 「 Express 」 版限制使用 INotifyPropertyChanged 層面 10 個類別,因此這可能表示貨幣成本。Fody,相反地,是免費的 NuGet 套件,讓它看起來似乎無法抗拒的首選。不論如何,請考慮任何 AOP 架構與您所撰寫的程式碼不完全相同的程式碼您將...執行和偵錯。

第三個方法

處理此的替代方式是使用物件導向設計︰ 具有本身會負責引發事件的內容 ! 不是特別革命性概念,但它不是我發現我自己的專案之外。在最基本的形式,它看起來可能如下所示︰

public class NotifyProperty<T>
{
  public NotifyProperty(INotifyPropertyChanged owner, string name, T initialValue);
  public string Name { get; }
  public T Value { get; }
  public void SetValue(T newValue);
}

其概念是,您讓屬性在其名稱和擁有者的參考,並讓它執行引發 PropertyChanged 事件的工作,如下所示︰

public void SetValue(T newValue)
{
  if(newValue != m_value)
  {
    m_value = newValue;
    m_owner.PropertyChanged(m_owner, new PropertyChangedEventArgs(Name));
  }
}

問題在於,這實際上不會運作︰ 我不能這樣引發事件,從另一個類別。我需要某種好讓我引發 PropertyChanged 事件合約主控類別:,完全負責的工作是一種介面,因此我建立一個︰

public interface IRaisePropertyChanged
{
  void RaisePropertyChanged(string propertyName)
}

此介面之後,我實際上可以實作 NotifyProperty.SetValue:

public void SetValue(T newValue)
{
  if(newValue != m_value)
  {
    m_value = newValue;
    m_owner.RaisePropertyChanged(this.Name);
  }
}

實作 IRaisePropertyChanged: 需要屬性擁有者,以實作介面並不表示每個檢視模型類別將會需要某些重複使用,如所示 [圖 1。第一個部分是必要的任何類別來實作 INotifyPropertyChanged;第二個部分是新 IRaisePropertyChanged 特有的。請注意,由於 RaisePropertyChanged 方法不適用於一般用途,我喜歡明確實作。

[圖 1 實作 IRaisePropertyChanged 所需的程式碼

// PART 1: required for any class that implements INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
  // In C# 6, you can use PropertyChanged?.Invoke.
  // Otherwise I'd suggest an extension method.
  var toRaise = PropertyChanged;
  if (toRaise != null)
    toRaise(this, args);
}
// PART 2: IRaisePropertyChanged-specific
protected virtual void RaisePropertyChanged(string propertyName)
{
  OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
// This method is only really for the sake of the interface,
// not for general usage, so I implement it explicitly.
void IRaisePropertyChanged.RaisePropertyChanged(string propertyName)
{
  this.RaisePropertyChanged(propertyName);
}

我無法將此未定案放在基底類別,並擴充它,使其看起來像是帶給我回到我前面的討論。畢竟,如果我套用 CallerMemberName RaisePropertyChanged 方法,我基本上已經歷第一種技術,因此重點何在? 在這兩種情況下,我可以只複製定案給其他類別,如果它們不能衍生自基底類別。

相較於先前的基底類別方法的其中一個主要差異是,在此情況下沒有實際的邏輯中沒有現成的;所有的邏輯被封裝在 NotifyProperty 類別。正在檢查之前引發此事件是簡單的邏輯,但最好還是不將其複製,是否已變更屬性值。會發生什麼情況,請考慮如果您想要使用不同的 IEqualityComparer 進行檢查。此模型中,您必須改變 NotifyProperty 類別。即使您有多個類別具有相同的 IRaisePropertyChanged 定案,每個實作有利於所做的變更至 NotifyProperty 而不需要變更任何程式碼本身。不論您可能想要介紹的任何行為變更,IRaisePropertyChanged 程式碼是很可能會發生變更。

將項目放在一起︰ 現在我有檢視模型需要實作的介面和 NotifyProperty 類別會用來將進行資料繫結的屬性。最後一個步驟建構 NotifyProperty;為此,您仍然需要屬性的名稱,以某種方式傳遞。如果您幸運使用 C# 6,輕鬆地利用 nameof 運算子。如果沒有,您可以改為建立的運算式,幫助 NotifyProperty 例如所使用的擴充方法 (不幸的是,也就沒地方 CallerMemberName 為了這次的):

public static NotifyProperty<T> CreateNotifyProperty<T>(
  this IRaisePropertyChanged owner,
  Expression<Func<T>> nameExpression, T initialValue)
{
  return new NotifyProperty<T>(owner,
    ObjectNamingExtensions.GetName(nameExpression),
    initialValue);
}
// Listing of GetName provided earlier

使用這個方法,您將仍需花費成本,但只有當建立物件,而不是每次屬性變更時反映。如果仍然過於昂貴 (您要建立多個物件),您就可以永遠快取 GetName,呼叫,並保留,以檢視模型類別中的靜態唯讀值。任一情況下,如 [圖 2 顯示簡單的檢視模型的範例。

[圖 2 基本 ViewModel 與 NotifyProperty

public class LogInViewModel : IRaisePropertyChanged
{
  public LogInViewModel()
  {
    // C# 6
    this.m_userNameProperty = new NotifyProperty<string>(
      this, nameof(UserName), null);
    // Extension method using expressions
    this.m_userNameProperty = this.CreateNotifyProperty(() => UserName, null);
  }
  private readonly NotifyProperty<string> m_userNameProperty;
  public string UserName
  {
    get
    {
      return m_userNameProperty.Value;
    }
    set
    {
      m_userNameProperty.SetValue(value);
    }
  }
  // Plus the IRaisePropertyChanged code in Figure 1 (otherwise, use a base class)
}

繫結和我所說的名稱重新命名︰ 時,它是討論另一個資料繫結問題的好時機。安全地引發 PropertyChanged 事件,而不需要硬式編碼字串是一半生存重構。資料繫結本身是另一半。如果您重新命名用來在 XAML 中繫結的屬性,成功,我應該說,我們無法保證 (例如,請參閱 bit.ly/1WCWE5m)。

替代方法是以手動方式在程式碼後置檔案中的資料繫結的程式碼。例如:

// Constructor
public LogInDialog()
{
  InitializeComponent();
  LogInViewModel forNaming = null;
  m_textBoxUserName.SetBinding(TextBox.TextProperty,
    ObjectNamingExtensions.GetName(() => forNaming.UserName);
  // Or with C# 6, just nameof(LogInViewModel.UserName)
}

它是僅供運用運算式功能,該 null 物件看起來有點怪,但運作 (您不需要它有 nameof 存取)。

我個人認為這項技術,但是我不要辨識取捨。一端加上,如果重新命名的使用者名稱屬性,我可以確信重構將運作。另一項重要優點是 [尋找所有參考],就如預期般都運作。

減號一邊,它不一定簡單而自然進行繫結,在 XAML 中,而且還能避免我將 UI 設計 「 獨立 」。 我只是無法使用而不需要變更程式碼,例如重新設計 Blend 工具中的外觀。此外,這項技術並不用於資料範本。您可以擷取該範本到自訂控制項,但是投入更多心力。

總而言之,便可以彈性地變更 「 資料模型 」 端,代價是在 「 檢視 」 端的彈性。整體來說,是由您是否優點無法證明這種方式宣告繫結。

「 具衍生性 」 的屬性

稍早所述的情況,它都會不引發 PropertyChanged 事件,也就是針對這些屬性的值取決於其他屬性特別方便。我說過,取決於 FirstName 和 LastName 的 FullName 屬性的簡單範例。我實作此案例的目標是來納入這些基底的 NotifyProperty 物件 (FirstName 和 LastName),以及要從這些計算衍生的函式 (例如 FirstName.Value +""+ LastName.Value),賺,將會自動處理對我的其餘部分的屬性物件。若要啟用此功能,有幾個要我原始 NotifyProperty 我進行的調整。

第一個工作是個別 ValueChanged 事件 NotifyProperty 上的公開 (expose)。衍生的屬性會接聽其基礎的屬性,此事件並回應藉由計算新值 (和引發適當的 PropertyChanged 事件本身)。擷取介面,以封裝一般 NotifyProperty 功能 IProperty < T >,為第二項工作。除此之外,這可讓我有衍生屬性來自其他衍生內容。產生的介面很簡單,此處所列 (NotifyProperty 對應的變更是很簡單,因此它們不會列出)︰

public interface IProperty<TValue>
{
  string Name { get; }
  event EventHandler<ValueChangedEventArgs> ValueChanged;
  TValue Value { get; }
}

建立 DerivedNotifyProperty 類別看起來直接明瞭,,直到您啟動想把各個部分拼湊起來。基本概念基礎屬性中,因為泛型立即計算,但某些新值的函式會執行可能造成問題。沒有任何可行的方式,來納入多個不同的屬性類型︰

// Attempted constructor
public DerivedNotifyProperty(IRaisePropertyChanged owner,
  string propertyName, IProperty<T1> property1, IProperty<T2> property2,
  Func<T1, T2, TDerived> derivedValueFunction)

我可以改為使用靜態建立方法,以避開上半年 (接受多個泛型型別) 的問題︰

static DerivedNotifyProperty<TDerived> CreateDerivedNotifyProperty
  <T1, T2, TDerived>(this IRaisePropertyChanged owner,
  string propertyName, IProperty<T1> property1, IProperty<T2> property2,
  Func<T1, T2, TDerived> derivedValueFunction)

但是衍生的屬性仍然必須接聽 ValueChanged 事件的每一個基底屬性。解決此問題,需要兩個步驟。首先,我會擷取 ValueChanged 事件到個別的介面︰

public interface INotifyValueChanged // No generic type!
{
  event EventHandler<ValueChangedEventArgs> ValueChanged;
}
public interface IProperty<TValue> : INotifyValueChanged
{
  string Name { get; }
  TValue Value { get; }
}

這可讓非泛型 INotifyValueChanged,而不是泛型 IProperty < T > 中 DerivedNotifyProperty。其次,我必須計算新值時不包含泛型︰ 我需要原始 derivedValueFunction 可接受兩個泛型參數,可建立新的匿名函式不需要任何參數,相反地,它會參考傳遞的兩個屬性的值。換句話說,我將建立 closure。您可以看到下列程式碼中的程序︰

static DerivedNotifyProperty<TDerived> CreateDerivedNotifyProperty
  <T1, T2, TDerived>(this IRaisePropertyChanged owner,
  string propertyName, IProperty<T1> property1, IProperty<T2> property2,
  Func<T1, T2, TDerived> derivedValueFunction)
{
  // Closure
  Func<TDerived> newDerivedValueFunction =
    () => derivedValueFunction (property1.Value, property2.Value);
  return new DerivedNotifyProperty<TValue>(owner, propertyName,
    newDerivedValueFunction, property1, property2);
}

新的 「 值衍生 」 函式為只 Func < TDerived > 不含任何參數。現在 DerivedNotifyProperty 不需要知道基礎的屬性型別,因此值得高興的是可以建立一個從不同類型的多個屬性。

其他的微妙影響時,會呼叫衍生值的函式。明顯的實作是接聽 ValueChanged 事件,每個基礎屬性,並呼叫函式,每當屬性變更時,但相同作業中的多個基礎屬性變更時,這是效率不佳 (假設清除表單的 「 重設 」 的按鈕)。進一步了解是產生隨選的值 (和快取),並使它失效,如果基礎屬性的任何變更。Lazy < T > 是完美的方式來實作此功能。

您可以看到 DerivedNotifyProperty 類別中的縮寫的清單 [圖 3。請注意,類別會接受任意數目的屬性來接聽 — 列出只有建立方法為基礎的兩個屬性,雖然我在上建立其他多載會接受一個基礎屬性、 三個基本屬性等等。

[圖 3 核心實作 DerivedNotifyProperty

public class DerivedNotifyProperty<TValue> : IProperty<TValue>
{
  private readonly IRaisePropertyChanged m_owner;
  private readonly Func<TValue> m_getValueProperty;
  public DerivedNotifyProperty(IRaisePropertyChanged owner,
    string derivedPropertyName, Func<TValue> getDerivedPropertyValue,
    params INotifyValueChanged[] valueChangesToListenFor)
  {
    this.m_owner = owner;
    this.Name = derivedPropertyName;
    this.m_getValueProperty = getDerivedPropertyValue;
    this.m_value = new Lazy<TValue>(m_getValueProperty);
    foreach (INotifyValueChanged valueChangeToListenFor in valueChangesToListenFor)
      valueChangeToListenFor.ValueChanged += (sender, e) => RefreshProperty();
  }
  // Name property and ValueChanged event omitted for brevity 
  private Lazy<TValue> m_value;
  public TValue Value
  {
    get
    {
      return m_value.Value;
    }
  }
  public void RefreshProperty()
  {
    // Ensure we retrieve the value anew the next time it is requested
    this.m_value = new Lazy<TValue>(m_getValueProperty);
    OnValueChanged(new ValueChangedEventArgs());
    m_owner.RaisePropertyChanged(Name);
  }
}

請注意基礎屬性可能來自不同的擁有者。例如,假設您有與 IsAddressValid 屬性位址檢視模型。您也可以包含兩個位址檢視模型、 帳單及送貨地址順序檢視模型。是合理的作法是結合 IsAddressValid 屬性的子位址檢視模型,因此只有當兩個位址都有效,您可以提交訂單父順序檢視模型上建立 IsOrderValid 屬性。若要這樣做,位址檢視模型會公開兩個 bool IsAddressValid {get;} 和 IProperty < bool > IsAddressValidProperty {get;},以便建立參考子 IsAddressValidProperty 物件 DerivedNotifyProperty 順序檢視模型。

DerivedNotifyProperty 的用處

我已經提供了衍生屬性 FullName 範例相當自然,但我還是要討論一些實際的使用案例,並將其繫結至一些設計原則。我只被初步上一個範例︰ IsValid。這是非常簡單且功能強大的方式,例如停用 [儲存] 按鈕,在表單上。請注意,沒有任何會強迫您只在 UI 檢視模型的內容中使用這項技術。您可以使用它來驗證商務物件,它們只需要實作 IRaisePropertyChanged。

衍生的屬性都非常實用,其中第二種情況是在 「 向下切入 」 案例中。簡單的範例,請考慮選取國家/地區,其中選取國家/地區會填入的城市清單的下拉式方塊。您可以是 NotifyProperty,而且有 GetCitiesForCountry 方法,建立將會自動保持同步時變更所選的國家/地區 DerivedNotifyProperty AvailableCities SelectedCountry。

我已在其中使用 NotifyProperty 物件的第三個區域是指出物件是否為 「 忙碌。 」 物件會被視為忙碌,應該停用特定的 UI 功能,而可能是使用者應該會看到進度列指示器。這看似簡單的案例中,但會有微妙的地方這裡逛在很多。

第一個部分追蹤物件是否為忙碌中;在簡單案例中,可以這麼做有布林 NotifyProperty。不過,通常會發生的事就物件是 「 忙碌 」 的多個原因︰ 假設我要載入的資料,可能是以平行方式的幾個區域。整體 「 忙碌中 」 狀態應該相依於這些項目是否仍在進行中。幾乎聽起來就像是衍生的屬性,但它的工作是笨拙 (甚至無法)︰ 我必須針對每個可能的作業來追蹤是否正在進行中的屬性。相反地,我想要執行類似下列的每個作業,使用單一 IsBusy 屬性︰

try
{
  IsBusy.SetValue(true);
  await LongRunningOperation();
}
finally
{
  IsBusy.SetValue(false);
}

若要啟用此功能,我建立 IsBusyNotifyProperty 類別來延伸 NotifyProperty < bool >,把,「 忙碌計數 」。 使 SetValue(true) 增加該計數和 SetValue(false) 減少它覆寫 setvalue 巨集。當計數從 0 到 1,則只呼叫基底。SetValue(true),以及當它會從 1 0 時,基底。SetValue(false)。如此一來,啟動 IsBusy 變成 true 只一次,並執行它再度 false 它們全部完成時,才在多個未完成的作業結果。您可以看到程式碼下載中的實作。

之前都是談論 「 忙碌 」 的側邊的事項︰ 我可以繫結 」 是忙碌中 」 的進度列指示器的可見性。不過,停用 UI,我需要相反。「 是忙碌中 」,則為 true 時,「 啟用 UI 」 應為 false。

XAML 可以別的 IValueConverter,將值轉換為 (或) 顯示表示法。無所不在的範例是 BooleanToVisibilityConverter — 在 XAML 中,項目的 「 可視性 」 不布林值,但而不是列舉值所描述。這表示不可能直接繫結項目的可見性至布林值屬性 (例如 IsBusy);您要繫結值,也會使用轉換程式。例如:

<StackPanel Visibility="{Binding IsBusy,
  Converter={StaticResource BooleanToVisibilityConverter}}" />

我提到 「 讓 UI 「 是 」 忙碌中; 相反它可能會想要建立值轉換器反轉布林值屬性,然後使用它來執行此作業︰

<Grid IsEnabled="{Binding IsBusy,
   Converter={StaticResource BooleanToInverseConverter}}" />

事實上,我建立了 DerivedNotifyProperty 類別之前,這是最簡單的方法。這是相當繁瑣的工作建立個別的屬性、 接到是 IsBusy,反向並引發適當的 PropertyChanged 事件。現在,輕而易舉的事,但沒有該假造的障礙 (也就是取巧) 已開啟的也可以使用 IValueConverter 深入了解。

最後,檢視 — 不過可能會實作 (WPF 或 Windows Form,例如,或甚至是主控台應用程式是一種檢視),應在基礎的應用程式,所發生的視覺效果 (或 「 投射 」) 並不負責決定機制和商務規則的情形。在此情況下,IsBusy 和 IsEnabled 剛好彼此相關,因此可感知的事實是實作細節。不繼承,停用 UI 應該與相關專為應用程式是否忙碌。

按照現況,我灰色區域中,將它視為並不會認為您如果您想使用的值轉換器來實作此功能。不過,我可以將多更強的案例範例加入另一項。讓我們假設,就會失去網路存取,如果應用程式應該也停用 UI (和顯示面板,表示這種情況)。嗯,這可讓三種情況︰ 如果應用程式忙碌中,我應該停用 UI,並顯示進度面板。如果應用程式的網路存取權,我應該也停用 UI,並顯示 「 中斷 」 的面板。第三個這種情況時,應用程式已連線並不忙碌,並因此,準備好接受輸入。

想要實作此而不需要個別的 [IsEnabled] 屬性是很冗長,因此最佳的情況。您可以使用 MultiBinding,但這是仍然沒什麼優點,並不支援在所有環境中。最後,awkwardness 種通常表示更好的方法,現在我們知道有是︰ 此邏輯比較容易處理檢視模型內。現在,真的並非易公開兩個 NotifyProperties、 IsBusy 及 IsDisconnected,然後再建立 [DerivedNotifyProperty,IsEnabled,只有當這兩個都是 false,則為 true。

如果您進入 IValueConverter 路由,直接繫結 UI 的 [啟用] 狀態 IsBusy (使用轉換器來反轉它),會有相當多的工作,現在要做。如果您改為公開不同的衍生的 IsEnabled 屬性,加入這個新的位元的邏輯比較少的工作,並繫結本身 IsEnabled 甚至不需要變更。這是好徵兆,你的權限項目。

總結

此架構的版面配置的旅程,但獎勵是現在,我可以實作屬性變更通知,而不需要重複的不變,而沒有魔法字串和的重構支援。我的檢視模型不需要特定的基底類別的邏輯。我可以建立衍生的屬性,也會引發適當的變更通知,而不需要太多額外的介入。最後,我看到的程式碼是正在執行的程式碼。而我得到的開發物件導向設計與架構相當簡單。我希望您發現自己的專案中很有用。


標記 Sowul 開始之後, 已配置的.NET 開發人員,並共用其豐富的 Microsoft.NET Framework 和 SQL Server 透過他的紐約諮商,SolSoft 解決方案的架構和效能專業知識。 與他連絡 mark@solsoftsolutions.com。如果您發現他的想法有趣,而且想要訂閱他的電子報, eepurl.com/_k7YD

衷心感謝以下技術專家對本文的審閱: Francis Cheung (Microsoft) 與爾斯 Malm (Zebra 技術)
Francis Cheung 是 Microsoft 模式和實務群組的首席開發人員。Francis 一直參與包括 Prism 專案的各種陣列。他目前致力於 Azure 的相關指引。

Charles Malm 是遊戲時,.NET、 Web 軟體工程師和 RealmSource,LLC 的創辦人。