View에서 읽기 전용 종속성 속성의 현재 상태를 항상 알고있는 ViewModel을 작성하고 싶습니다.
특히 내 GUI에는 FlowDocument에서 한 번에 한 페이지를 표시하는 FlowDocumentPageViewer가 포함되어 있습니다. FlowDocumentPageViewer는 CanGoToPreviousPage 및 CanGoToNextPage라는 두 개의 읽기 전용 종속성 속성을 노출합니다. 내 ViewModel이 항상이 두 View 속성의 값을 알고 있기를 바랍니다.
OneWayToSource 데이터 바인딩으로 이것을 할 수 있다고 생각했습니다.
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
이것이 허용되면 완벽 할 것입니다. FlowDocumentPageViewer의 CanGoToNextPage 속성이 변경 될 때마다 새 값이 ViewModel의 NextPageAvailable 속성으로 푸시됩니다.
불행히도 이것은 컴파일되지 않습니다. ‘CanGoToPreviousPage’속성이 읽기 전용이며 마크 업에서 설정할 수 없다는 오류가 발생 합니다. 분명히 읽기 전용 속성은 어떤 종류의 데이터 바인딩도 지원하지 않으며 해당 속성 과 관련하여 읽기 전용 인 데이터 바인딩도 지원하지 않습니다 .
내 ViewModel의 속성을 DependencyProperties로 만들고 OneWay 바인딩을 다른 방향으로 만들 수는 있지만 우려 사항 분리 위반에 대해 열광하지는 않습니다. ).
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
속성은 단순히 부착 SizeChanged
의 경우 FrameworkElement
. 핸들에서 ObservedWidth
및 ObservedHeight
속성을 업데이트 합니다. 인체 공학적는 Width
와 Height
뷰 모델은 동기화 항상 ActualWidth
하고 ActualHeight
의 UserControl
.
아마도 완벽한 솔루션은 아니지만 (동의합니다-읽기 전용 DP 는OneWayToSource
바인딩 을 지원 해야 함 ) 작동하며 MVVM 패턴을 유지합니다. 분명히, ObservedWidth
그리고 ObservedHeight
드프가되어 있지 읽기 전용입니다.
업데이트 : 위에 설명 된 기능을 구현하는 코드는 다음과 같습니다.
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);
}
}
답변
ActualWidth 및 ActualHeight뿐만 아니라 적어도 읽기 모드에서 바인딩 할 수있는 모든 데이터와 함께 작동하는 범용 솔루션을 사용합니다.
ViewportWidth 및 ViewportHeight가 뷰 모델의 속성 인 경우 마크 업은 다음과 같습니다.
<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();
}
}
답변
다른 사람이 관심이 있다면 여기에 Kent 솔루션의 근사치를 코딩했습니다.
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
}
앱에서 자유롭게 사용하십시오. 잘 작동한다. (Kent에게 감사합니다!)
답변
여기에 내가 블로그에 올린이 “버그”에 대한 또 다른 솔루션이 있습니다.
OneWayToSource Binding for ReadOnly Dependency Property
두 개의 종속성 속성 인 Listener와 Mirror를 사용하여 작동합니다. 리스너는 OneWay를 TargetProperty에 바인딩하고 PropertyChangedCallback에서 OneWayToSource에 바인딩 된 Mirror 속성을 Binding에 지정된대로 업데이트합니다. 나는 그것을 호출하고 다음 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>
여기에서 데모 프로젝트를 다운로드하십시오 .
여기에는 소스 코드와 간단한 샘플 사용이 포함되어 있습니다 . 구현 세부 정보에 관심이있는 경우 내 WPF 블로그를 방문 하십시오.
마지막 참고 사항은 .NET 4.0 이후로 OneWayToSource 바인딩이 업데이트 한 후 소스에서 값을 다시 읽어 오기 때문에이를위한 기본 제공 지원에서 훨씬 더 멀리 떨어져 있습니다.
답변
나는 Dmitry Tashkinov의 솔루션을 좋아합니다! 그러나 그것은 디자인 모드에서 내 VS를 추락했습니다. 그래서 OnSourceChanged 메서드에 줄을 추가했습니다.
개인 정적 무효 OnSourceChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { if (! ((bool) DesignerProperties.IsInDesignModeProperty.GetMetadata (typeof (DependencyObject)). DefaultValue)) ((DataPipe) d) .OnSourceChanged (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);
}
}