如何在保留原本所有樣式/繫結和使用者設定值的情況下,設定和還原 WPF 相依性屬性的值

如何在保留原本所有樣式/繫結和使用者設定值的情況下,設定和還原 WPF 相依性屬性的值

WPF 備份某控制項的一些屬性,做一些神奇的操作,然後再還原這些屬性。

最後更新 2021/11/9 上午10:08
吕毅
預計閱讀 4 分鐘
分類
WPF
標籤
.NET WPF

WPF 備份某控制項的一些屬性,做一些神奇的操作,然後再還原這些屬性。多麼司空見慣的操作呀!然而怎麼備份卻是值得研究的問題。直接指派?那一定是因為你沒踩到一些坑。

本文內容

  • 場景和問題
  • 解決方法和原理
  • 延伸

場景和問題

現在,我們假想一個場景(為了寫程式方便):

  1. 有一個視窗,設定了一些樣式屬性
  2. 現在需要將這個視窗設為全螢幕,這要求修改一些原來的屬性(WPF 自帶那個設定有 bug,我會另寫一篇部落格說明)
  3. 取消設定視窗全螢幕後,之前修改的那些屬性要「完美」還原

一般可能會這麼寫:

private Window _window;
private WindowStyle _oldStyle;

private void OnEnterFullScreen()
{
    _oldStyle = _window.WindowStyle;
    _window.WindowStyle = WindowStyle.None;
}

private void OnExitFullScreen()
{
    _window.WindowStyle = _oldStyle;
}

然而:

  • 如果某人在 WindowStyle 上設了個動態的樣式怎麼辦?——那當然是不再動態了呀(因為覆蓋了樣式值)
  • 如果某人在 WindowStyle 上設定了繫結怎麼辦?——那當然也是不再生效了呀(因為繫結被你覆蓋了)

解決方法和原理

因為各大 WPF 入門書籍都說到了 WPF 相依性屬性的優先權機制,所以大家應該基本都知道這個。不了解的,可以立刻去這裡看看:相依性屬性值優先順序 - WPF Microsoft Docs

是這樣的優先順序:強制 > 動畫 > 本機值 > 範本 > 隱含樣式 > 樣式觸發程序 > 範本觸發程序 > 樣式 > 預設樣式 > 屬性繼承 > 中繼資料預設值。

而我們透過在 XAML 或 C# 程式碼中直接指派,設定的是「本機值」。因此,如果設定了本機值,那麼更低優先權的樣式當然就全部失效了。

那麼繫結呢?繫結在相依性屬性優先權中並不存在。繫結實際上是透過「本機值」來實現的,將一個繫結運算式設定到「本機值」中,然後在需要值的時候,會 ProvideValue 提供值。所以,如果再設定了本機值,那麼繫結的設定就被覆蓋掉了。

但是,SetCurrentValue 就是幹這件事的!

SetCurrentValue 設計為在不改變相依性屬性任何既有值的情況下,設定屬性目前的值。

_window.SetCurrentValue(Window.WindowStyleProperty, WindowStyle.None);

那麼,只需要還原 SetCurrentValue 所做的修改,就還原了此相依性屬性一切設定的值:

_window.InvalidateProperty(Window.WindowStyleProperty);

注意不是 ClearValue,那會清除本機值。

然而還差一點,繫結如果在你應用 SetCurrentValue 期間有改變,那麼這次的指派並不會讓繫結立即生效,所以我們還需要手工再讓繫結重新更新值:

BindingOperations.GetBindingExpression(_window, Window.WindowStyleProperty)?.UpdateTarget();

那麼,綜合起來,本文一開始的程式碼將更新成如下形式:

private Window _window;

private void OnEnterFullScreen()
{
    _window.SetCurrentValue(Window.WindowStyleProperty, WindowStyle.None);
}

private void OnExitFullScreen()
{
    _window.InvalidateProperty(Window.WindowStyleProperty);
    BindingOperations.GetBindingExpression(_window, Window.WindowStyleProperty)?.UpdateTarget();
}

延伸

將程式碼變得通用一點:

static void ApplyTempProperty(DependencyObject d, DependencyProperty dp, object tempValue)
{
    d?.SetCurrentValue(dp, tempValue);
}

static void RestoreProperty(DependencyObject d, DependencyProperty dp)
{
    d.InvalidateProperty(dp);
    BindingOperations.GetBindingExpression(d, dp)?.UpdateTarget();
}

本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/change-and-restore-wpf-dependency-value-without-disabling-the-declared-use-of-the-property.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

如果你想持續閱讀我的最新部落格,請點擊 RSS 訂閱,或者前往 CSDN 關注我的主頁

知識共享授權條款本作品採用 創用CC 姓名標示-非商業性-相同方式分享 4.0 國際授權條款 進行授權。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的授權發布。如有任何疑問,請 與我聯繫 (walter.lv@qq.com)

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2025/1/26

WPF 藉助自訂 XML 檔案實現國際化

本文詳細介紹了在WPF程式中使用自訂XML檔案實現國際化的方法,包括安裝必備NuGet套件、動態獲取語言清單、動態切換語言、在程式碼和XAML介面中使用翻譯字串等內容,同時提供了原始碼連結,幫助開發者輕鬆實現WPF應用程式的國際化。

繼續閱讀