Hallo,
ich möchte zum darstellen von Daten ein UserControl erstellen, dass sauber nach MVVM aufgebaut ist und ein eigenes ViewModel besitzt, allerdings auch Dependancy Properties, damit ich bei der Einbindung im XAML Werte wie das Maximum etc. per Binding setzen kann.
Bislang sieht es so aus, dass ich im XAML.cs die Dependancy Properties erstelle:
public int Maximum
{
get { return (int)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(int), typeof(Surface2D), new FrameworkPropertyMetadata(new PropertyChangedCallback(MaximumPropertyChanged)));
private static void MaximumPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
// do something
}
und im ViewModel (von INotifyPropertyChanged abgeleitet) die restlichen Properties, für das View erstelle. Auf die Dependancy Properties kann ich im View dann auch zugreifen, wie z.B. so (der Name des View ist einfach userCOntrol):
<TextBlock Text="{Binding Maximum, ElementName=userControl}"/>
Meine Frage ist jetzt, wie ich das sauber hinbekomme ohne an zwei verschiedenen Stellen Daten halten und pflegen zu müssen. Gerade weil ich in Anhängigkeit einiger Dependancy Properties auch Werte für das ViewModel generieren möchte, ist das so ja arg unschön.
Es ist nicht verkehrt, wenn dein UC mehrere DP hat.
Wenn dir das nicht gefällt kannst du auch eine Klasse erstellen in der alle notwendigen Informationen drinnen sind. Dann erstellst du eine DP namens DataSource oder so ähnlich und verwendest diese Klasse als Typ.
Mir geht es nicht primär um die Dependancy Properties; davon habe ich gerne mehrere, die optional übergeben werden.
Nur wo packe ich die DP am besten hin (geht ja wohl nur im xalm.cs??) und wie mache ich die die saubere Kommunikation mit dem ViewModel? Im xaml.cs komme ich ja über den DataContext an das ViewModel aber ist das sauber und komme ich irgendwie sauber an die Daten der DPs aus dem ViewModel?
Wieso hat dein ViewModel eine DP?
Das hat da nichts verloren. Damit erstellt man Eigenschaften für ein Control/UIElement. Ein ViewModel benachrichtigt über die Implementierung von INotifyPropertyChanged
.
Das sollte man nicht durcheinanderwerfen.
Gehen würde es zwar, macht aber wenig Sinn, denn bei einem ViewModel wird eine Änderung einer Eigenschaft kein Neuzeichnen des Viewmodels auslösen (was man per DP ja vermerken kann).
Mein ViewModel hat keine DPs, das möchte ich auch nicht. Ich registriere die DPs in der xaml.cs, möchte nur eben die Werte auch im ViewModel verwenden können.
Kann ich auf die Werte sauber zugreifen? Vom ViewModel aus ist ja die View nicht bekannt, worin die DPs registriert wurden.
Als Workaround habe ich das so gelöst, dass ich im ViewModel eine gleichlautende Property zu jeder DP im View erstellt habe und über den PropertyChangedCallback die Werte im VM aktualisiere. Das ist aber nicht wirklich sauber:
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(Surface2D));//, new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPropertyChanged)));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((Surface2DViewModel)((Surface2D)sender).DataContext).GetType().GetProperty(args.Property.Name).SetValue(((Surface2DViewModel)((Surface2D)sender).DataContext), ((Surface2D)sender).GetValue(args.Property));
}
Setz doch einfach ein TwoWay-Binding?
Dann wird jeder Wert der DP automatisch ins ViewModel übertragen
Abgesehen davon habe ich bisher kaum sinnvolle Anwendungsfälle von DependencyProperties gesehen, die sich nicht einfacher mit einem ViewModel lösen ließen.
Einzige Ausnahmen sind Controls, die so allgemein gehalten sind, dass ein ViewModel sich nicht lohnt. Ein NumericUpDown zum Beispiel, dafür würde ich kein ViewModel schreiben, sondern DPs erstellen.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Ok, ich habe das jetzt auf ein View mit CodeBehind begrenzt und kein ViewModel mehr. Dadurch habe ich das Problem mit dem Updaten des ViewModels aus dem CodeBehind beim Setzen der DPs nicht mehr.
Allerdings kriege ich bei dem Binding im MainWindow das Problem, dass das UserControl seinen eigenen DataContext besitzt und ich gerade auf dem Schlauch stehe, wie ich beim Binding auf das ViewModel des MainWindows zugreife. Hier der XAML Code:
<Surface:Surface2D Maximum="{Binding MaximumMVW, BindsDirectlyToSource=True, FallbackValue=30}"/>
MaximumMVW ist die Maximum Property im MainViewModel, es wird aber der DataContext des UserCOntrols verwendet, indem diese Property natürlich nicht definiert ist.
Du könntest beim Binding das MainControl als RelativeSource angeben und dort dann die entsprechende Eigenschaft des DataContexts (den musst du dann explizit beim Binding mit angeben) binden.
Alternativ kannst du auch einen BindingProxy (wie z.B. hier beschrieben: http://stackoverflow.com/a/22074985/1120056) nutzen.
Danke! Das hat perfekt funktioniert mit der RelativeSource:
<Surface:Surface2D Maximum="{Binding DataContext.MaximumMVW, BindsDirectlyToSource=True, FallbackValue=30, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ViewModel:MainWindow}}}"/>
Was ich persönlich lieber mag, ist ein BindingProxy
Das funktioniert dann auch, wenn das Control mit dem Binding nicht im VisualTree liegt, beim ContextMenu ist das glaube der Fall.
Sähe dann so aus:
<ViewModel:MainWindow.Resources>
<utils:BindingProxy x:Key="myBindingProxy" Data="{Binding}" />
</ViewModel:MainWindow.Resources>
<Surface:Surface2D Maximum="{Binding Data.MaximumMVW, Source={StaticResource myBindingProxy}}"/>
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Hier mal ein Beispiel-UserControl mit DPs am Control und einem ViewModel für das Control:
namespace WpfApp1
{
public class MyControlViewModel : BindableObject
{
#region Property Value
private int _value;
public int Value
{
get { return _value; }
set
{
if ( _value != value )
{
_value = value;
OnPropertyChanged( );
}
}
}
#endregion
}
}
namespace WpfApp1
{
/// <summary>
/// Interaktionslogik für MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent( );
}
public int Maximum
{
get { return (int) GetValue( MaximumProperty ); }
set { SetValue( MaximumProperty, value ); }
}
// Using a DependencyProperty as the backing store for Maximum. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register( "Maximum", typeof( int ), typeof( MyControl ), new PropertyMetadata( 30 ) );
public MyControlViewModel ItemSource
{
get { return (MyControlViewModel) GetValue( ItemSourceProperty ); }
set { SetValue( ItemSourceProperty, value ); }
}
// Using a DependencyProperty as the backing store for ItemSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemSourceProperty =
DependencyProperty.Register( "ItemSource", typeof( MyControlViewModel ), typeof( MyControl ), new PropertyMetadata( null ) );
}
}
<UserControl x:Class="WpfApp1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Height="Auto" Width="Auto">
<Grid>
<Slider
DataContext="{Binding ItemSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyControl}}}"
Maximum="{Binding Maximum, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyControl}}}"
Value="{Binding Path=Value}"/>
</Grid>
</UserControl>
namespace WpfApp1
{
public class MainWindowViewModel : BindableObject
{
public MainWindowViewModel()
{
Foo = new MyControlViewModel { Value = 20, };
Maximum = 100;
}
#region Property Foo
private MyControlViewModel _foo;
public MyControlViewModel Foo
{
get { return _foo; }
set
{
if ( _foo != value )
{
_foo = value;
OnPropertyChanged( );
}
}
}
#endregion
#region Property Maximum
private int _maximum;
public int Maximum
{
get { return _maximum; }
set
{
if ( _maximum != value )
{
_maximum = value;
OnPropertyChanged( );
}
}
}
#endregion
}
}
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<local:MyControl ItemSource="{Binding Path=Foo}" Maximum="{Binding Path=Maximum}"/>
</Grid>
</Window>
und schon stehst du dir nicht mehr auf den Füßen herum 😁