1. ホーム
  2. wpf

[解決済み] 読み取り専用のGUIプロパティをViewModelにプッシュバックする

2022-07-02 06:16:34

質問

Viewから読み取り専用の依存プロパティの現在の状態を常に知っているViewModelを書きたいと思っています。

具体的には、私のGUIは、FlowDocumentから一度に1ページを表示するFlowDocumentPageViewerを含んでいます。FlowDocumentPageViewer は、CanGoToPreviousPage および CanGoToNextPage という 2 つの読み取り専用の依存性プロパティを公開します。私は、私のViewModelがこれらの2つのViewプロパティの値を常に知っているようにしたい。

私はOneWayToSourceデータバインディングでこれを行うことができると考えました。

<FlowDocumentPageViewer
    CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>

これが許可されていれば、完璧です。FlowDocumentPageViewerのCanGoToNextPageプロパティが変更されるたびに、新しい値はViewModelのNextPageAvailableプロパティに押し込まれるでしょう、これはまさに私が望むことです。

残念ながら、これはコンパイルされません。次のようなエラーが表示されます。 'CanGoToPreviousPage' プロパティは読み取り専用であり、マークアップから設定することはできません。 どうやら読み取り専用のプロパティは 任意の のようなデータバインディングをサポートしません。

私はViewModelのプロパティをDependencyPropertiesにすることができ、他の方法で行くOneWayバインディングを作ることができますが、私は分離概念違反について狂っていません(ViewModelはビューへの参照を必要とし、MVVMデータバインドは避けることになっている)。

FlowDocumentPageViewerはCanGoToNextPageChangedイベントを公開せず、私はDependencyPropertyから変更通知を取得する良い方法を知りません。

ビューの読み取り専用プロパティへの変更を ViewModel に通知し続けるにはどうしたらよいですか。

どのように解決するのですか?

はい、過去にこの方法を ActualWidthActualHeight プロパティがあり、どちらも読み取り専用になっています。付属のビヘイビアを作成し、そのビヘイビアに ObservedWidthObservedHeight が付属しています。また Observe プロパティがあり、これは最初のフックアップに使用されます。使い方は以下のようになります。

<UserControl ...
    SizeObserver.Observe="True"
    SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
    SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"

つまり、ビューモデルには WidthHeight プロパティと常に同期している ObservedWidthObservedHeight が付属するプロパティです。このプロパティは Observe プロパティは単に SizeChanged イベントにアタッチするだけです。 FrameworkElement . ハンドルの中で、それはその ObservedWidthObservedHeight のプロパティがあります。つまり WidthHeight は、常にビューモデルの ActualWidthActualHeightUserControl .

おそらく完璧な解決策ではありません(私もそう思います - 読み取り専用DPの をサポートします。 OneWayToSource のバインディングをサポートする必要があります)、しかしそれは動作し、MVVMパターンを支持しています。明らかに ObservedWidthObservedHeight DPは ではなく 読み出し専用です。

UPDATE: 上記の機能を実装したコードはこちらです。

public static class SizeObserver
{
    public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
        "Observe",
        typeof(bool),
        typeof(SizeObserver),
        new FrameworkPropertyMetadata(OnObserveChanged));

    public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
        "ObservedWidth",
        typeof(double),
        typeof(SizeObserver));

    public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
        "ObservedHeight",
        typeof(double),
        typeof(SizeObserver));

    public static bool GetObserve(FrameworkElement frameworkElement)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        return (bool)frameworkElement.GetValue(ObserveProperty);
    }

    public static void SetObserve(FrameworkElement frameworkElement, bool observe)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        frameworkElement.SetValue(ObserveProperty, observe);
    }

    public static double GetObservedWidth(FrameworkElement frameworkElement)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        return (double)frameworkElement.GetValue(ObservedWidthProperty);
    }

    public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        frameworkElement.SetValue(ObservedWidthProperty, observedWidth);
    }

    public static double GetObservedHeight(FrameworkElement frameworkElement)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        return (double)frameworkElement.GetValue(ObservedHeightProperty);
    }

    public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight)
    {
        frameworkElement.AssertNotNull("frameworkElement");
        frameworkElement.SetValue(ObservedHeightProperty, observedHeight);
    }

    private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = (FrameworkElement)dependencyObject;

        if ((bool)e.NewValue)
        {
            frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
            UpdateObservedSizesForFrameworkElement(frameworkElement);
        }
        else
        {
            frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
        }
    }

    private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
    }

    private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
    {
        // WPF 4.0 onwards
        frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth);
        frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight);

        // WPF 3.5 and prior
        ////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth);
        ////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight);
    }
}