Przenoszenie właściwości GUI tylko do odczytu z powrotem do ViewModel
Chcę napisać model widoku, który zawsze zna aktualny stan niektórych właściwości zależności Tylko do odczytu z widoku.
W szczególności mój GUI zawiera FlowDocumentPageViewer, który wyświetla jedną stronę na raz z dokumentu FlowDocument. FlowDocumentPageViewer wyświetla dwie właściwości zależności Tylko do odczytu o nazwie CanGoToPreviousPage i CanGoToNextPage. Chcę, aby mój model widoku zawsze znał wartości tych dwóch właściwości widoku.
Pomyślałem, że mogę to zrobić z Onewaytosource databinding:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
Gdyby było to dozwolone, byłoby idealnie: za każdym razem, gdy zmieniana jest właściwość FlowDocumentPageViewer CanGoToNextPage, nowa wartość zostanie zepchnięta w dół do Właściwości Nextpageavailable ViewModel, co jest dokładnie tym, czego chcę.
Niestety, to nie kompiluje się: pojawia się błąd mówiący 'cangotopreviouspage' właściwość jest tylko do odczytu i nie może być ustawiona ze znaczników. najwyraźniej właściwości tylko do odczytu nie obsługują żadnych rodzaj databindingu, a nawet databindingu, który jest tylko do odczytu w odniesieniu do tej właściwości.
Mógłbym sprawić, że właściwości mojego ViewModel będą zależne od właściwości, i zrobić Wiązanie OneWay idąc w drugą stronę, ale nie szaleję za naruszeniem separacji obaw (ViewModel wymagałby odniesienia do widoku, którego MVVM databinding ma unikać).
FlowDocumentPageViewer nie ujawnia zdarzenia CanGoToNextPageChanged i nie znam żadnego dobrego sposobu na zmianę powiadomienia z DependencyProperty, w skrócie tworzenia innego DependencyProperty do wiązania go, co wydaje się tutaj przesadą.
Jak mogę informować mój model widoku o zmianach właściwości widoku tylko do odczytu?
6 answers
Tak, robiłem to w przeszłości za pomocą właściwości ActualWidth
i ActualHeight
, z których obie są tylko do odczytu. Stworzyłem dołączone zachowanie, które ma ObservedWidth
i ObservedHeight
dołączone właściwości. Posiada również właściwość Observe
, która jest używana do początkowego podłączenia. Użycie wygląda tak:
<UserControl ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
Tak więc model widoku ma właściwości Width
i Height
, które są zawsze zsynchronizowane z dołączonymi właściwościami ObservedWidth
i ObservedHeight
. Właściwość Observe
po prostu dołącza się do SizeChanged
zdarzenia FrameworkElement
. W uchwyt, aktualizuje swoje właściwości ObservedWidth
i ObservedHeight
. Ergo, Width
i Height
modelu widoku jest zawsze zsynchronizowany z ActualWidth
i ActualHeight
z UserControl
.
Być może nie jest to idealne rozwiązanie (zgadzam się - DPS tylko do odczytu powinien wspierać wiązania OneWayToSource
), ale działa i podtrzymuje wzór MVVM. Oczywiście, ObservedWidth
i ObservedHeight
DPs są , a nie Tylko do odczytu.
UPDATE: oto kod, który implementuje funkcjonalność opisaną powyżej:
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);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-08 11:53:59
Używam uniwersalnego rozwiązania, które działa nie tylko z ActualWidth i ActualHeight, ale także z dowolnymi danymi, które można powiązać przynajmniej w trybie odczytu.
Znaczniki wyglądają tak, pod warunkiem, że viewportwidth i viewportheight są właściwościami modelu widoku
<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>
Oto kod źródłowy elementów niestandardowych
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();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-08 11:57:10
Jeśli ktoś jeszcze jest zainteresowany, zakodowałem przybliżenie rozwiązania Kenta tutaj:
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
}
Możesz używać go w swoich aplikacjach. Działa dobrze. (Dzięki Kent!)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-20 12:14:05
Oto kolejne rozwiązanie tego "błędu" , o którym pisałem tutaj:
Wiązanie Onewaytosource dla właściwości zależności ReadOnly
Działa przy użyciu dwóch właściwości zależności, Listener I Mirror. Listener jest związany OneWay z TargetProperty i w Właściwościchangedcallback aktualizuje właściwość Mirror, która jest związana OneWayToSource do tego, co zostało określone w wiązaniu. Nazywam ją PushBinding
i może być ustawiona na dowolnej właściwości zależności Tylko do odczytu, takiej jak to
<TextBlock Name="myTextBlock"
Background="LightBlue">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
Pobierz Projekt Demo Tutaj .
Zawiera kod źródłowy i krótkie przykładowe użycie, lub odwiedź mój blog WPF {[5] } jeśli jesteś zainteresowany szczegółami implementacji.
ostatnia uwaga, od. NET 4.0 jesteśmy jeszcze dalej od wbudowanego wsparcia dla tego, ponieważ Wiązanie OneWayToSource odczytuje wartość z powrotem ze źródła po jej zaktualizowaniu
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-03 16:02:22
Podoba mi się rozwiązanie Dmitrija Taszkinowa! Jednak rozbił mój VS w trybie projektowania. Dlatego dodałem linię do metody OnSourceChanged:
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)) ((DataPipe)d).OnSourceChanged(e); }
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 13:34:55
Myślę, że można to zrobić trochę prościej:
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);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-09 13:44:03