Wpf 将只读GUI属性推回ViewModel

Wpf 将只读GUI属性推回ViewModel,wpf,data-binding,mvvm,readonly,Wpf,Data Binding,Mvvm,Readonly,我想编写一个ViewModel,它总是从视图中知道某些只读依赖属性的当前状态 具体地说,我的GUI包含一个FlowDocumentPageViewer,它一次显示来自FlowDocument的一个页面。FlowDocumentPageViewer公开了两个只读依赖属性,分别称为CangotPreviousPage和CangotNextPage。我希望我的ViewModel始终知道这两个视图属性的值 我想我可以用OneWayToSource数据绑定来实现这一点: <FlowDocumentP

我想编写一个ViewModel,它总是从视图中知道某些只读依赖属性的当前状态

具体地说,我的GUI包含一个FlowDocumentPageViewer,它一次显示来自FlowDocument的一个页面。FlowDocumentPageViewer公开了两个只读依赖属性,分别称为CangotPreviousPage和CangotNextPage。我希望我的ViewModel始终知道这两个视图属性的值

我想我可以用OneWayToSource数据绑定来实现这一点:

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

如果允许这样做,那将是完美的:每当FlowDocumentPageViewer的CangotNextPage属性发生更改时,新值将被下推到ViewModel的NextPageAvailable属性中,这正是我想要的

不幸的是,这无法编译:我得到一个错误,说“CanGoToPreviousPage”属性是只读的,不能从标记中设置。显然只读属性不支持任何类型的数据绑定,甚至不支持该属性的只读数据绑定

我可以将ViewModel的属性设置为DependencyProperties,并将单向绑定设置为另一种方式,但我并不热衷于关注点分离冲突(ViewModel需要对视图的引用,MVVM数据绑定应该避免这种情况)

FlowDocumentPageViewer不公开CanGoToNextPageChanged事件,我也不知道有什么好方法可以从DependencyProperty获取更改通知,除了创建另一个DependencyProperty将其绑定到它之外,这在这里似乎有些过分


如何将视图只读属性的更改通知给ViewModel?

是的,我以前使用过
实际宽度
实际高度
属性,这两个属性都是只读的。我创建了一个附加行为,该行为具有
ObservedWidth
ObservedHeight
附加属性。它还有一个
Observe
属性,用于执行初始连接。用法如下所示:


如果其他人感兴趣,我在这里编写了肯特解决方案的近似值:

class SizeObserver
{
#区域“观察”
公共静态bool GetObserve(FrameworkElement元素)
{
返回(bool)元素GetValue(ObserveProperty);
}
公共静态无效设置(
框架元素(元素、布尔值)
{
元素设置值(ObserveProperty,value);
}
公共静态只读从属属性ObserveProperty=
DependencyProperty.RegisterAttached(“观察”)、typeof(bool)、typeof(SizeObserver),
新的UIPropertyMetadata(错误,未观察到更改);
观测到的静态空隙率变化(
DependencyObject depObj,DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem=depObj作为FrameworkElement;
if(elem==null)
返回;
如果(e.NewValue为bool==false)
返回;
if((bool)e.NewValue)
elem.SizeChanged+=OnSizeChanged;
其他的
elem.SizeChanged-=OnSizeChanged;
}
静态无效大小已更改(对象发送器,路由目标e)
{
如果(!Object.ReferenceEquals(发送方,例如OriginalSource))
返回;
FrameworkElement elem=e.作为FrameworkElement的原始源;
if(elem!=null)
{
设置观测宽度(要素、要素实际宽度);
设置观测高度(要素、要素实际高度);
}
}
#端区
#区域“观测宽度”
公共静态双GetObservedWidth(DependencyObject obj)
{
返回(双精度)对象GetValue(ObservedWidthProperty);
}
公共静态void SetObservedWidth(DependencyObject obj,双值)
{
对象设置值(ObservedWidthProperty,值);
}
//使用DependencyProperty作为ObservedWidth的后备存储。这将启用动画、样式、绑定等。。。
public static readonly dependencProperty ObservedWidthProperty=
DependencyProperty.RegisterAttached(“ObservedWidth”、typeof(double)、typeof(SizeObserver)、新UIPropertyMetadata(0.0));
#端区
#区域“观测高度”
公共静态双GetObservedHeight(DependencyObject obj)
{
返回(双精度)对象GetValue(ObservedHeightProperty);
}
公共静态void SetObservedHeight(DependencyObject对象,双值)
{
对象设置值(ObservedHeightProperty,值);
}
//使用DependencyProperty作为ObservedHeight的后备存储。这将启用动画、样式、绑定等。。。
公共静态只读从属属性ObservedHeightProperty=
DependencyProperty.RegisterAttached(“ObservedHeight”、typeof(double)、typeof(SizeObserver)、new UIPropertyMetadata(0.0));
#端区
}

您可以在应用程序中随意使用它。它工作得很好。(感谢Kent!)

我使用一种通用解决方案,它不仅适用于实际宽度和实际高度,而且适用于至少在读取模式下可以绑定到的任何数据

如果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>

以下是自定义元素的源代码

公共类数据管道
{
#区域数据管道(附加的从属属性)
公共静态只读从属属性DataPipesProperty=
DependencyProperty.RegisterAttached(“数据管道”,
类型(数据管道收集),
类型(数据管道),
新的UIPropertyMetadata(空);
公共静态void SetDataPipes(DependencyObject o,DataPipeCollection值)
{
o、 设置值(DataPipesProperty,value);
}
公共静态DataPipeCollection GetDataPipes(DependencyObject o)
{
返回(DataPipeCollection)o.GetValue(DataPipesProperty);
}
#端区
}
公共类DataPipeCollection:FreezableCollection
{
}
公共类数据管道
<TextBlock Name="myTextBlock"
           Background="LightBlue">
    <pb:PushBindingManager.PushBindings>
        <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
        <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
    </pb:PushBindingManager.PushBindings>
</TextBlock>
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)) ((DataPipe)d).OnSourceChanged(e); }
behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}"