WPF Binding window height to ViewModel property - c#

This question has been asked once before however it did not get a satisfactory answer...
I am following the MVVM design archetype and I would like to be able to change the width and height of the window. To do this I decided to create two properties in my ViewModel:
private int xWidth;
public int XWidth
{
get { return xWidth; }
set
{
xWidth = value;
RaisePropertyChanged("XWidth");
}
}
private int yHeight;
public int YHeight
{
get { return yHeight; }
set
{
yHeight = value;
RaisePropertyChanged("YHeight");
}
}
I then bound the height and width to those properties:
Height="{Binding YHeight}" Width="{Binding XWidth}">
Finally I created an method which changes those values:
private void HomeExecute()
{
ShowMain = true;
ShowSearch = false;
YHeight = 350;
XWidth = 525;
}
This however is not working. When the method executes the window doesn't change size.
I know that the View is bound correctly to the ViewModel as other bindings work.
I also know that the method is being run as the ShowMain property gets changed.
I had a hunch that it might need a converter of sorts as I am passing to the width and height properties an int however my research didn't lead to anything.

I'm not sure, why the binding does not work, maybe it has something to do with the fact, that window is not just regular control, but some kind of wrapper around WinAPI. However, you can still use good old code behind event-based approach, even without breaking MVVM separation of concerns.
I have written MVVM sample, that does not use xaml binding, but implements two-way binding using "plain eventhandlers":
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
Loaded += delegate
{
Height = ViewModel.YHeight;
Width = ViewModel.XWidth;
ViewModel.PropertyChanged += ViewModelOnPropertyChanged;
SizeChanged += MainWindow_SizeChanged;
};
Unloaded += delegate
{
ViewModel.PropertyChanged -= ViewModelOnPropertyChanged;
SizeChanged -= MainWindow_SizeChanged;
};
}
public MainWindowViewModel ViewModel
{
get { return (MainWindowViewModel)DataContext; }
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "YHeight")
{
Height = ViewModel.YHeight;
}
if (e.PropertyName == "XWidth")
{
Width = ViewModel.XWidth;
}
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.XWidth = e.NewSize.Width;
ViewModel.YHeight = e.NewSize.Height;
}
}
If you need to reuse this behaviour, you can move all the logic to behavior implemented as attached property or custom blend behavior
I recommend you to raise PropertyChanged only when property value changed. e.g if (xWidth != value) OnPropertyChanged("XWidth")

You could just add the binding mode "two way" to your height and width bindings. Just edit your XAML to look like this:
Height="{Binding YHeight, Mode=TwoWay}" Width="{Binding XWidth, Mode=TwoWay}">
That's it. You can now set the window size from your viewmodel.

Related

Bind ViewModel's property to UserControl's child control and itself DependencyProperty

What I want to do is to bind my usercontrol's label to a property value in ViewModel.
But I also want to get notified when the label changed ( and to do other work like extract the label's new value to modify grid width and so on).
How to do this?
what I did is:
Have a viewmodel with an Voltage property, which is what I want to display.
UnitVm.cs
private int m_V;
public int VoltInVm
{
get
{ return m_V; }
set
{
if (m_V != value)
{
Set<int>(ref m_V, value, nameof(Volt));
}
}
}
and my usercontrol: Unit.cs
public partial class Unit : UserControl
{
public static readonly DependencyProperty VoltProperty =
DependencyProperty.Register("Volt", typeof(int), typeof(Unit),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender, (o, e) => ((Unit)o).OnVoltChanged(o, e)));
private void OnVoltChanged(double dVolt)
{
double dWidth;
if (double.TryParse(strVal, out dWidth))
{
dWidth = dVolt / 380 * 100;
if (dWidth > 100)
dWidth = 100;
gridVolt.ColumnDefinitions[0].Width = new GridLength(dWidth, GridUnitType.Star);
gridVolt.ColumnDefinitions[1].Width = new GridLength(100 - dWidth, GridUnitType.Star);
}
}
public int Volt
{
get { return (int)GetValue(VoltProperty); }
set
{
SetValue(VoltProperty, value);
}
}
the DependencyProperty of VoltProperty is defined, and the work I want to do is written inside OnVoltChanged.
I mean when accepting change from ViewModel, I can call OnVoltChanged.
To use the usercontrol of Unit in a main window:
<DockPanel DataContext="{Binding UnitVm, Source={StaticResource Locator}}">
<Viewbox
<Label x:Name="lblVolt" Content="{Binding VoltInVm}" />
</Viewbox>
</DockPanel>
lblVolt binding to UnitVm context can update with new voltage values correctly.
But how to bind to DependencyProperty of Volt in Unit?
And is this the right way?
Thanks in advance.
Ting
The view model property setter is not implemented correctly.
It should look like shown below, i.e. use the correct property name nameof(VoltInVm) and probably not check m_V != value before calling Set, because that is already done by the Set method.
private int voltInVm;
public int VoltInVm
{
get { return voltInVm; }
set { Set<int>(ref voltInVm, value, nameof(VoltInVm)); }
}
When you now bind the UserControl's property like
<local:Unit Volt="{Binding VoltInVm}"/>
the PropertyChangedCallback of the Volt dependency property will be called each time the VoltInVm property changes its value.
It is however unclear what (o, e) => ((Unit)o).OnVoltChanged(o, e) in the dependency property registration is supposed to be. It should certainly look like this:
(o, e) => ((Unit)o).OnVoltChanged((int)e.NewValue)
and the method should be declared with an int argument instead of double:
private void OnVoltChanged(int volt) ...
Or - certainly better - change all voltage property types to double, in the control and in the view model.

Silverlight with MVVM : How to access Event of the ViewModel from the View?

I have a MVVM application and somewhere in the application our company use a Third-Party that cannot use {Binding}. It's a component that draw shapes, etc. What I want it, when the ViewModel load from the persisted storage all shapes to notify the View to draw them. In a perfect world I would just have the take the Third-party and bind it to the ViewModel Shapes collection but I cannot.
From there, my idea was that I could get from the View the ViewModel (via the DataContext) and to hook the PropertyChanged event. The problem is that the DataContext is not yet initialized in the constructor, so it's NULL, and I cannot hook the event. Here is a sample of the code:
public CanvasView()
{
InitializeComponent();
((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged); //Exception Throw here because DataContext is null
}
void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Shapes")
{
DrawShapes();
}
}
How can I get information from my ViewModel to my View in that case?
All of the answers so far breaks the MVVM pattern with having code-behind on the view. Personally I would wrap the 3rd party control in a UserControl and then wire up a few dependency properties with property change events.
C#
public partial class MyWrappedControl : UserControl
{
public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register("Shapes", typeof(ObservableCollection<IShape>), typeof(MyWrappedControl),
new PropertyMetadata(null, MyWrappedControl.OnShapesPropertyChanged);
public ObservableCollection<IShape> Shapes
{
get { return (ObservableCollection<IShape>)GetValue(ShapesProperty); }
set { SetValue(ShapesProperty, value); }
}
private static void OnShapesPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyWrappedControl)o).OnShapesPropertyChanged(e);
}
private void OnShapesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// Do stuff, e.g. shapeDrawer.DrawShapes();
}
}
XAML
<UserControl
Name="MyWrappedControl"
x:Class="MyWrappedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<!-- your control -->
<shapeDrawerControl x:Name="shapeDrawer" />
</UserControl>
you could also attach your handler in the Loaded event.
public CanvasView()
{
InitializeComponent();
this.Loaded += this.ViewLoaded;
}
void ViewLoaded(object sender, PropertyChangedEventArgs e)
{
((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged);
}
void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Shapes")
{
DrawShapes();
}
}
I want to comment Dennis Roche answer.
Really, in this case we can use wrap approach, because we need to redraw view when Shapes collection changed. But view model logic can be too complex, and ,for instance, instead of redraw on PropertyChanged we should redraw on some custom event (f.i. ModelReloadEvent). In this case, wrapping doesn't help, but subscription on this event does, as in Muad'Dib solution - view model use event based communication with view, but this event should be view specific.
Using code-behind with View specific logic doesn't break MVVM. Yes, this code can be decorated with behavior/action, but using code behind - just simple solution.
Also, take a look at this view on MVVM. According to structure, ViewModel knows about abstract IView.If you worked with Caliburn/Caliburn.Micro MVVM frameworks you remember ViewAware class and IViewAware, which allows get view in view model.
So, more flexible solution I guess is next:
View:
public class CanvasView() : ICanvasView
{
public CanvasView()
{
InitializeComponent();
}
public void DrawShapes()
{
// implementation
}
}
ICanvasView:
public interface ICanvasView
{
void DrawShapes();
}
CanvasViewModel:
public class CanvasViewModel : ViewAware
{
private ObservableCollection<IShape> _shapes;
public ObservableCollection<IShape> Shapes
{
get
{
return _shapes;
}
set
{
_shapes = value;
NotifyOfPropertyChange(() => Shapes);
RedrawView();
}
}
private void RedrawView()
{
ICanvasView abstractView = (ICanvasView)GetView();
abstractView.DrawShapes();
}
}
Use the DataContextChanged event on the View (Window or UserControl)
public CanvasView()
{
InitializeComponent();
Action wireDataContext += new Action ( () => {
if (DataContext!=null)
((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged);
});
this.DataContextChanged += (_,__) => wireDataContext();
wireDataContext();
}
void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Shapes")
{
DrawShapes();
}
}
update: Here is a documented way to get DataContextChanged in Silverlight 3 and 4 http://www.lhotka.net/weblog/AddingDataContextChangedInSilverlight.aspx

Data binding in WPF on button click

I am trying to implement data binding, and to have TextBox's text to be update once I click on some button.
XAML:
<TextBox Text="{Binding Path=Output}" />
Code:
public MainWindow()
{
InitializeComponent();
DataContext = Search;
Search.Output = "111";
}
public SearchClass Search = new SearchClass();
private void button1_Click(object sender, RoutedEventArgs e)
{
Search.Output = "222";
}
public class SearchClass
{
string _output;
public string Output
{
get { return _output; }
set { _output = value; }
}
}
When I execute the program, I see "111", so the binding from MainWindow() works, but if I click a button - the text in the TextBox is not updated (but in the debugger I see that button1_Click is executed and Search.Output is now equal to "222"). What am I doing wrong?
You should implement INotifyPropertyChanged in your SearchClass and then in setter raise the event:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string Output
{
get { return _output; }
set
{
_output = value;
PropertyChanged(this, new PropertyChangedEventArgs("Output"));
}
}
If I understood right, SearchClass is the DataContext for your TextBlock. In this case implementing as above would help.
When WPF see some class as the source of Binding - it tries to cast it to INotifyPropertyChanged and subscribe to PropertyChanged event. And when event is raised - WPF updates the binding associated with sender (first argument of PropertyChanged). It is the main mechanism that makes binding work so smoothly.
You have to implement the INotifyPropertyChanged interface on your SearchClass class. This is how binder values are notified their source values have changed. It displays the "111" value because it hasn't been laid out yet (more or less), but will won't update after that until you implement that interface.

INotifyPropertyChanged implementation, binding does not work

I'm learning to create an app for WP7 (Mango), and somehow having this problem.
This is not actual code to my app, but a simplified version of the same problem. I think mostly it's due to lack of deep understanding of how binding works.
XAML.
<TextBlock x:Name="PageTitle" Text="{Binding Title}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
Code Behind.
private MainPageViewModel viewModel;
// Constructor
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
viewModel = new MainPageViewModel();
this.DataContext = viewModel;
}
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
viewModel.GenerateTitle();
}
And my ViewModel.
private static int counter = 0;
private string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
OnPropertyChanged("Title");
}
}
}
public MainPageViewModel()
{
title = "Init";
}
public void GenerateTitle()
{
if (counter == 0)
title = "0"; // Title = "0" will work fine.
if (counter == 1)
title = "1";
counter++;
}
Problem is, it only update the binding once inside my ViewModel constructor, so the title is "Init".
Any call to the GenerateTitle does not update the property. It works if I use 'Title' instead of 'title', which calls the setter.
Or I should really use 'Title'? I haven't done much C#, so my understanding of OOP is not that great yet.
The following line in the setter is what notifies the observers that he value has changed:
OnPropertyChanged("Title");
When you use the private field value, that method isn't being called so the observers aren't being notified that the value of the property has changed.
Because of that, you need to use the Property if you want the observers to be notified. You could also add the OnPropertyChanged("Title"); line to your GenerateTitle() method, but I would recommend just using the Property.
Yes you have to use Title as title just sets the field whereas Title runs the setter which raises the event

Binding to a ScrollViewer's ViewportWidth and ViewportHeight

I am using the Model-View-ViewModel architecture in a WPF application I am building, and I would like a specific ViewModel to actually be reactive to the size of the view (not a normal use-case of the MVVM approach, I know).
Essentially, I have a ScrollViewer object and I want the viewmodel to observe the width and height of the scrollviewer and then be able to do things accordingly depending on what that width and height are.
I'd like to do something like this:
<ScrollViewer ViewportWidth="{Binding Path=MyViewportWidth, Mode=OneWayToSource}" ViewportHeight="{Binding Path=MyViewportHeight, Mode=OneWayToSource}" />
But of course this is impossible to do because "ViewportWidth" and "ViewportHeight" cannot be "bound to" (a.k.a. act as binding targets) because they are read-only dependency properties (even though I am not writing to them at all in this binding since it is OneWayToSource).
Anyone know of a good method to be able to do something like this?
You could try running something OnLoaded or OnResizeChanged that updates the viewmodel
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
ViewModel vm = sv.DataContext as ViewModel;
vm.ScrollViewerHeight = sv.ViewportHeight;
vm.ScrollViewerWidth = sv.ViewportWidth;
}
Ok, this is a really old question, but I thought I'd share for posterity, since I've solved this one myself. The best solution I've found is to create a user control that derives from the ScrollView class and implements the properties you want - which are of course linked to the non-bindable properties of the base class.
You can use the OnPropertyChanged function to monitor those properties and keep the values in sync.
Here's the full code-behind of my custom usercontrol called DynamicScrollViewer. Notice that I have four bindable dependency properties called DynamicHorizontalOffset, DynamicVerticalOffset, DynamicViewportWidth, and DynamicViewportHeight.
The two offset properties allow both read and write control of the offset, while the viewport properties are essentially read-only.
I had to use this class when creating a complex animation editor control in which various components (labels at the left, nodes in the middle, timeline at top) needed to scroll synchronously, but only in limited aspects, and were all bound to common external scrollbars. Think of locking a section of rows in spreadsheet, and you get the idea.
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class DynamicScrollViewer : ScrollViewer
{
public DynamicScrollViewer()
{
InitializeComponent();
}
public double DynamicHorizontalOffset
{
get { return (double)GetValue(DynamicHorizontalOffsetProperty); }
set { SetValue(DynamicHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicHorizontalOffsetProperty =
DependencyProperty.Register("DynamicHorizontalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicVerticalOffset
{
get { return (double)GetValue(DynamicVerticalOffsetProperty); }
set { SetValue(DynamicVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicVerticalOffsetProperty =
DependencyProperty.Register("DynamicVerticalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportWidth
{
get { return (double)GetValue(DynamicViewportWidthProperty); }
set { SetValue(DynamicViewportWidthProperty, value); }
}
public static readonly DependencyProperty DynamicViewportWidthProperty =
DependencyProperty.Register("DynamicViewportWidth", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportHeight
{
get { return (double)GetValue(DynamicViewportHeightProperty); }
set { SetValue(DynamicViewportHeightProperty, value); }
}
public static readonly DependencyProperty DynamicViewportHeightProperty =
DependencyProperty.Register("DynamicViewportHeight", typeof(double), typeof(DynamicScrollViewer));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DynamicVerticalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetVerticalOffset(DynamicVerticalOffset);
}
else if (e.Property == DynamicHorizontalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetHorizontalOffset(DynamicHorizontalOffset);
}
else if (e.Property == HorizontalOffsetProperty)
{
DynamicHorizontalOffset = (double)e.NewValue;
}
else if (e.Property == VerticalOffsetProperty)
{
DynamicVerticalOffset = (double)e.NewValue;
}
else if (e.Property == ViewportWidthProperty)
{
DynamicViewportWidth = (double)e.NewValue;
}
else if (e.Property == ViewportHeightProperty)
{
DynamicViewportHeight = (double)e.NewValue;
}
}
}
}

Categories

Resources