programing

읽기 전용 GUI 속성을 ViewModel로 되돌리는 중

cafebook 2023. 4. 13. 21:09
반응형

읽기 전용 GUI 속성을 ViewModel로 되돌리는 중

View에서 일부 읽기 전용 종속성 속성의 현재 상태를 항상 알고 있는 ViewModel을 작성하려고 합니다.

특히 GUI에는 FlowDocument에서 한 번에 한 페이지씩 표시되는 FlowDocumentPageViewer가 포함되어 있습니다.FlowDocumentPageViewer는 CanGoToPreviousPage와 CanGoToNextPage라는2개의 읽기 전용 의존관계 속성을 표시합니다.View Model이 이 두 View 속성의 값을 항상 알고 있어야 합니다.

OneWayToSource 데이터 바인딩으로 이 작업을 수행할 수 있습니다.

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

이것이 허용된다면 완벽할 것입니다. FlowDocumentPageViewer의 CanGoToNextPage속성이 변경될 때마다 ViewModel의 NextPageAvailable속성에 새로운 값이 푸시됩니다.이것이 바로 제가 원하는 것입니다.

유감스럽게도, 이것은 컴파일 되지 않습니다.'CanGoToPreviousPage' 속성이 읽기 전용이며 마크업에서 설정할 수 없다는 오류가 나타납니다.읽기 전용 속성은 어떤 종류의 데이터 바인딩도 지원하지 않습니다. 해당 속성과 관련하여 읽기 전용인 데이터 바인딩도 지원하지 않습니다.

ViewModel의 속성을 DependencyProperties로 하고 OneWay 바인딩을 반대로 할 수는 있지만 관심사 분리 위반에는 관심이 없습니다(ViewModel은 View에 대한 참조가 필요하며, 이는 MVVM 데이터 바인딩이 방지되어야 합니다).

FlowDocumentPageViewer는 CanGoToNextPageChanged 이벤트를 공개하지 않습니다.또한 DependencyProperty에서 변경 알림을 받을 수 있는 좋은 방법은 알 수 없습니다.이러한 방법은 바인드할 다른 DependencyProperty를 작성하는 것 외에는 알 수 없습니다.

뷰의 읽기 전용 속성 변경 내용을 ViewModel에 계속 알려주려면 어떻게 해야 합니까?

네, 제가 예전에 이 일을 했던 적이 있어요.ActualWidth그리고.ActualHeight둘 다 읽기 전용 속성입니다.내가 만든 첨부 동작은ObservedWidth그리고.ObservedHeight첨부 속성.또,Observe초기 후크업 실행에 사용되는 속성.사용 방법은 다음과 같습니다.

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

뷰 모델에는Width그리고.Height항상 와 동기화되는 속성ObservedWidth그리고.ObservedHeight첨부 속성.Observe재산은 단순히 에 붙어 있다SizeChangedFrameworkElement에서는 . 핸핸음음, 음음음 its its its its its its its its its its its its its its its its its its its its its its ObservedWidth ★★★★★★★★★★★★★★★★★」ObservedHeight 에르고,Width ★★★★★★★★★★★★★★★★★」Height의 경우 의뷰 is is of 、 、 of 、 of 、 of of of of of of of of 。ActualWidth ★★★★★★★★★★★★★★★★★」ActualHeightUserControl.

아마도 완벽한 솔루션이 아닐 수도 있습니다(동의합니다. 읽기 전용 DP는OneWayToSourceMVVM을 사용하다 그 명,는,ObservedWidth ★★★★★★★★★★★★★★★★★」ObservedHeightDP는 읽기 전용이 아닙니다.

업데이트: 위에서 설명한 기능을 구현하는 코드는 다음과 같습니다.

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

저는 ExperialWidth와 ExperialHeight뿐만 아니라 적어도 읽기 모드에서 바인딩할 수 있는 모든 데이터와 연동되는 범용 솔루션을 사용하고 있습니다.

마크업은 ViewportWidth 및 Viewport를 지정하면 다음과 같습니다.높이는 뷰 모델의 특성입니다.

<Canvas>
    <u:DataPiping.DataPipes>
         <u:DataPipeCollection>
             <u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}"
                         Target="{Binding Path=ViewportWidth, Mode=OneWayToSource}"/>
             <u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"
                         Target="{Binding Path=ViewportHeight, Mode=OneWayToSource}"/>
          </u:DataPipeCollection>
     </u:DataPiping.DataPipes>
<Canvas>

커스텀 요소의 소스 코드는 다음과 같습니다.

public class DataPiping
{
    #region DataPipes (Attached DependencyProperty)

    public static readonly DependencyProperty DataPipesProperty =
        DependencyProperty.RegisterAttached("DataPipes",
        typeof(DataPipeCollection),
        typeof(DataPiping),
        new UIPropertyMetadata(null));

    public static void SetDataPipes(DependencyObject o, DataPipeCollection value)
    {
        o.SetValue(DataPipesProperty, value);
    }

    public static DataPipeCollection GetDataPipes(DependencyObject o)
    {
        return (DataPipeCollection)o.GetValue(DataPipesProperty);
    }

    #endregion
}

public class DataPipeCollection : FreezableCollection<DataPipe>
{

}

public class DataPipe : Freezable
{
    #region Source (DependencyProperty)

    public object Source
    {
        get { return (object)GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }
    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register("Source", typeof(object), typeof(DataPipe),
        new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged)));

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((DataPipe)d).OnSourceChanged(e);
    }

    protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e)
    {
        Target = e.NewValue;
    }

    #endregion

    #region Target (DependencyProperty)

    public object Target
    {
        get { return (object)GetValue(TargetProperty); }
        set { SetValue(TargetProperty, value); }
    }
    public static readonly DependencyProperty TargetProperty =
        DependencyProperty.Register("Target", typeof(object), typeof(DataPipe),
        new FrameworkPropertyMetadata(null));

    #endregion

    protected override Freezable CreateInstanceCore()
    {
        return new DataPipe();
    }
}

관심 있는 사람이 있다면, 켄트의 솔루션에 대한 근사치를 여기에 코드화해 두겠습니다.

class SizeObserver
{
    #region " Observe "

    public static bool GetObserve(FrameworkElement elem)
    {
        return (bool)elem.GetValue(ObserveProperty);
    }

    public static void SetObserve(
      FrameworkElement elem, bool value)
    {
        elem.SetValue(ObserveProperty, value);
    }

    public static readonly DependencyProperty ObserveProperty =
        DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver),
        new UIPropertyMetadata(false, OnObserveChanged));

    static void OnObserveChanged(
      DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement elem = depObj as FrameworkElement;
        if (elem == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            elem.SizeChanged += OnSizeChanged;
        else
            elem.SizeChanged -= OnSizeChanged;
    }

    static void OnSizeChanged(object sender, RoutedEventArgs e)
    {
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        FrameworkElement elem = e.OriginalSource as FrameworkElement;
        if (elem != null)
        {
            SetObservedWidth(elem, elem.ActualWidth);
            SetObservedHeight(elem, elem.ActualHeight);
        }
    }

    #endregion

    #region " ObservedWidth "

    public static double GetObservedWidth(DependencyObject obj)
    {
        return (double)obj.GetValue(ObservedWidthProperty);
    }

    public static void SetObservedWidth(DependencyObject obj, double value)
    {
        obj.SetValue(ObservedWidthProperty, value);
    }

    // Using a DependencyProperty as the backing store for ObservedWidth.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ObservedWidthProperty =
        DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));

    #endregion

    #region " ObservedHeight "

    public static double GetObservedHeight(DependencyObject obj)
    {
        return (double)obj.GetValue(ObservedHeightProperty);
    }

    public static void SetObservedHeight(DependencyObject obj, double value)
    {
        obj.SetValue(ObservedHeightProperty, value);
    }

    // Using a DependencyProperty as the backing store for ObservedHeight.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ObservedHeightProperty =
        DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));

    #endregion
}

앱에서 자유롭게 사용하세요.잘 돼. (고마워 켄트!)

여기에서는, 이 「버그」에 대한 또 다른 대처법을 블로그에 게재하고 있습니다.
Dependency

'청취자 거울'입니다.Listener one OneWay 、 TargetProperty 、 Property Changed Callback 、 OneWayToSource mirror listener listener listener listener listener listener listener listener listener listener listener listener listener listener listener listener listener 。는 그것을 나는그 it it라고 부른다.PushBinding, 이러한 의 의존 할 수 .

<TextBlock Name="myTextBlock"
           Background="LightBlue">
    <pb:PushBindingManager.PushBindings>
        <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
        <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
    </pb:PushBindingManager.PushBindings>
</TextBlock>

여기에서 데모 프로젝트를 다운로드하십시오.
여기에는 소스 코드와 짧은 샘플 사용이 포함되어 있습니다.

마지막 메모는 이후입니다.NET 4.0은 OneWayToSource 바인딩을 업데이트한 후 소스로부터 값을 읽어내기 때문에 이 기능의 빌트인 지원과는 거리가 멀어져 있습니다.

드미트리 타슈키노프의 솔루션이 마음에 들어요!하지만 디자인 모드에서 VS가 충돌했습니다.그래서 OnSource Changed 메서드에 행을 추가했습니다.

Private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){(!(부울)Designer Properties(디자이너 속성)IsInDesignModeProperty 입니다.GetMetadata(type of(DependencyObject)).디폴트값)((DataPipe)d).OnSource Changed(e);
}

좀 더 간단하게 할 수 있을 것 같아요.

xaml:

behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}"

cs:

public class ReadOnlyPropertyToModelBindingBehavior
{
  public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached(
     "ReadOnlyDependencyProperty", 
     typeof(object), 
     typeof(ReadOnlyPropertyToModelBindingBehavior),
     new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged));

  public static void SetReadOnlyDependencyProperty(DependencyObject element, object value)
  {
     element.SetValue(ReadOnlyDependencyPropertyProperty, value);
  }

  public static object GetReadOnlyDependencyProperty(DependencyObject element)
  {
     return element.GetValue(ReadOnlyDependencyPropertyProperty);
  }

  private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  {
     SetModelProperty(obj, e.NewValue);
  }


  public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached(
     "ModelProperty", 
     typeof(object), 
     typeof(ReadOnlyPropertyToModelBindingBehavior), 
     new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

  public static void SetModelProperty(DependencyObject element, object value)
  {
     element.SetValue(ModelPropertyProperty, value);
  }

  public static object GetModelProperty(DependencyObject element)
  {
     return element.GetValue(ModelPropertyProperty);
  }
}

언급URL : https://stackoverflow.com/questions/1083224/pushing-read-only-gui-properties-back-into-viewmodel

반응형