WPF Window Aspect Ratio - wpf

I'm wondering how to maintain the aspect ratio (i.e.: 16x9) of a window in WPF upon resize--if possible in a way that leverages MVVM. As I'm new to both MVVM and WPF, I'm not sure where to begin. Thanks.

This may be difficult to do with a "pure" MVVM implementation, because you need to know which direction the resize happened (horizontally or vertically). Note that if both change at once (i.e. the user resizes by dragging the corner), you will need to decide which of these to use.
In your ViewModel, you will probably have a property named AspectRatio.
In your View, you will most likely override the OnRenderSizeChanged event. Its then a matter of taste whether you do the work in the view using the property from the ViewModel, or whether you pass the value to a property in the ViewModel to do the work, and bind to the new values.
Example 1: Do the work here
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * mViewModel.AspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * mViewModel.AspectRatio;
}
}
Example 2: Do the work in the ViewModel
View.xaml.cs
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
if (sizeInfo.WidthChanged)
{
viewModel.AspectWidth = sizeInfo.NewSize.Width;
}
else
{
viewModel.AspectHeight = sizeInfo.NewSize.Height;
}
}
ViewModel.cs
public Double AspectWidth
{
get { return mAspectWidth; }
set
{
// Some method that sets your property and implements INotifyPropertyChanged
SetValue("AspectWidth", ref mAspectWidth, value);
SetValue("AspectHeight", ref mAspectHeight, mAspectWidth * mAspectRatio);
}
}
public Double AspectHeight
{
get { return mAspectHeight; }
set
{
// Some method that sets your property and implements INotifyPropertyChanged
SetValue("AspectHeight", ref mAspectHeight, value);
SetValue("AspectWidth", ref mAspectWidth, mAspectHeight* mAspectRatio);
}
}
And your view (for example 2) would bind the window's width and height to the AspectWidth and AspectHeight properties in the viewmodel.
View.xaml
<Window Width="{Binding AspectWidth}"
Height="{Binding AspectHeight}">
</Window>
So, in either case, you override OnRenderSizeChanged. The details on how you implement that method are up to your tastes. I guess that Example #2 is closer to pure "MVVM" but it may also be overkill in this case.

Related

Updating a dependency property based on changes in the view model

I'm having some problems with data binding in WPF. Here's the scenario: I have made a user control which simulates a Dial Pad (i.e., an array of 12 buttons with the digits from '0' to '9' plus the '#' and 'Clear' keys). The control lives inside a class library and it's been implemented following the MVVM pattern, mainly because I need the components in the class library to be easily unit tested.
The view model for the control is quite simple, it basically updates a public "DialedNumber" string (which is internally connected to the model) every time the user presses a dial pad key button. The binding is working correctly and, by using the debugger, I can confirm that the "DialedNumber" variable inside the viewmodel is getting updated as I press button in the dial pad.
This DialPad control is used by a separate XAML file (Panel.xaml), which laids out several controls that belong to my custom class library.
Now, I'd like to add a TextBlock inside my Panel file in order to display the "DialedNumber" string held inside the DialPad. This is the code snippet in Panel.xaml:
<PanelControls:DialPad x:Name="MyDialPad" DialedNumber="55325"/>
<TextBlock Text="{Binding ElementName=MyDialPad, Path=DialedNumber}" />
The result I'm getting is that the textblock displays the correct number on start (i.e., "55325"), but its content doesn't get updated as I press the dial pad keys (even though the DialPad's viewmodel gets updated as I press new keys, as I've checked with the debugger).
Here's the code behind for the DialPad view:
public partial class DialPad : UserControl
{
public DialPad()
{
InitializeComponent();
DataContext = new DialPadViewModel();
}
public void DialedNumberChanged(object sender, EventArgs e)
{
return;
}
public DialPadViewModel DialPadViewModel
{
get { return DataContext as DialPadViewModel; }
}
public string DialedNumber
{
get
{
var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
return (dialPadViewModel != null) ? dialPadViewModel.DialedNumber : "";
}
set
{
var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
if (dialPadViewModel != null)
{
dialPadViewModel.DialedNumber = value;
}
}
}
}
Here's the DialPad view model:
public class DialPadViewModel : ObservableObject
{
public DialPadViewModel()
{
_dialPadModel = new DialPadModel();
}
#region Fields
private readonly DialPadModel _dialPadModel;
private ICommand _dialPadKeyPressed;
#endregion
#region Public Properties/Command
public DialPadModel DialPadModel
{
get { return _dialPadModel; }
}
public ICommand DialPadKeyPressedCommand
{
get
{
if (_dialPadKeyPressed == null)
{
_dialPadKeyPressed = new RelayCommand(DialPadKeyPressedCmd);
}
return _dialPadKeyPressed;
}
}
public string DialedNumber
{
get { return _dialPadModel.DialedNumber; }
set
{
_dialPadModel.DialedNumber = value;
RaisePropertyChanged("DialedNumber");
}
}
#endregion
#region Private Helpers
private void DialPadKeyPressedCmd(object parameter)
{
string keyPressedString = parameter.ToString();
if (keyPressedString.Length > 0)
{
if (char.IsDigit(keyPressedString[0]))
{
DialedNumber += keyPressedString[0].ToString(CultureInfo.InvariantCulture);
}
else if (keyPressedString == "C" || keyPressedString == "Clr" || keyPressedString == "Clear")
{
DialedNumber = "";
}
}
}
#endregion
}
Let me restate my problem: the textblock in Panel.xaml displays the correct number (55325) on start, but its value never gets updated as I press the DialPadButtons. I've placed a breakpoint inside DialPadKeyPressedCmd and I can confirm that the method gets executed everytime I press a key in the dial pad.
DependencyProperties are meant to point to some other property to get their value. So you can either point it to your DialPadViewModel.DialedNumber, or you can point it to some other string when the UserControl is used (either a binding or a hardcoded value like "551"), but you can't do both.
In your case, when someone binds to the DialedNumber dependency property, they are replacing the current value (the binding to DialPadViewModel.DialedNumber) with a new value.
Depending on how your code looks and what you want to do, there are a few ways around it.
First, you could insist that people who want to use your control also use your ViewModel, and don't make DialedNumber a public dependency property.
So instead of being allowed to create a custom class with a property of SomeOtherDialedNumber and binding
<DialPad DialedNumber="{Binding SomeOtherDialedNumber}">
they are forced to use the DialPadViewModel in their code anytime they want to use the DialPad control. For this to work, you would need to remove the this.DataContext = new DialPadViewModel in your code-behind the UserControl since the user will be providing the DialPadViewModel to your UserControl, and you can use an implicit DataTemplate to tell WPF to always draw DialPadViewModel with your DialPad UserControl.
<DataTemplate DataType="{x:Type DialPadViewModel}">
<local:DialPad />
</DataTemplate>
The other alternative I can think of is to synchronize your DependencyProperty with your ViewModel property with some PropertyChange notifications.
You would need to update DialPadViewModel.DialedNumber anytime the DialedNumber dependency property changes (You may need to use DependencyPropertyDescriptor.AddValueChanged for property change notification), and you would also have to write something to update the source of the DialedNumber dependency property anytime DialPadViewModel.DialedNumber changes.
Personally, if my UserControl has a ViewModel then I use the first option. If not, I get rid of the ViewModel entirely and build the logic for my UserControl in the code-behind, without a ViewModel.
The reason for this is that WPF works with two layers: a UI layer and a data layer. The DataContext is the data layer, and a ViewModel is typically part of the data layer. By setting the data layer (DataContext) explicitly in the UserControl's constructor, you are combining your data layer with your UI layer, which goes against one of the biggest reasons for using MVVM: separation of concerns. A UserControl should really just be a pretty shell only, and you should be able to place it on top of any data layer you want.
If you place your DialPad in your View, you can create a DialPadViewModel-Property (public+global) in your ViewViewModel:
public DialPadViewModel DialPadViewModel = new DialPadViewModel();
Now set the DataContext-Binding of your View to the ViewViewModel and bind the DialPads DataContext also to it, like
<local:DialPad DataContext="{Binding}"/>
Now you can bind to the properties in your DialPadViewModel:
<TextBox Text="{Binding Path=DialPadViewModel.DialedNumber}"/>
Thats how you can Access your DialPadViewModel from your View and your DialPad.
EDIT:
Now try changing your DialedNumber Property in your DialPad.xaml.cs like this:
public string DialedNumber
{
get
{
return DialPadViewModel.DialedNumber;
}
set
{
DialPadViewModel.DialedNumber = value;
}
}
EDIT 2: I found the Problem:
In your DialPad.xaml all your Commands were bound to the DialPadViewModel from the resources, while the TextBloc was bound to the DialPads DataContext, which is another instance of the DialPadViewModel.
So everytime you hit a DialPad-Button you changed the value of the DialedNumber from the resources' DPVM-instance not the DialedNumber from the DataContext's DPVM-instance.
It sounds like you can add a TextBox to your view and bind it's Text property to your view-model's DialedNumber property.
<TextBox Text="{Binding Path=DialedNumber}"></TextBox>
Your view-model property can look something like this:
private string _dialedNumber;
[DefaultValue("551")]
public string DialedNumber
{
get { return _dialedNumber; }
set
{
if (value == _dialedNumber)
return;
_dialedNumber= value;
_yourModel.DialedNumber= _dialedNumber;
this.RaisePropertyChanged("DialedNumber");
}
}
Let me know if I misunderstood your question.

MVVM pattern violation: MediaElement.Play()

I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.

Binding to a field of a non-dependancy object

In my .NET 4.0 project I've got an object that has public fields and this object neither implements INotifyPropertyChanged nor inherits DependencyObject, and it will never do. However, I need a mechanism to "bind" to fields of this object in my WPF control. I know I can't do it directly as binding requires a dependency property (or at least, properties and notifying property changes), so what can I do to implement the binding functionality I need. I've tried something like this in my WPF control:
void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public float Friction
{
get
{
if (CurrentObject != null)
{
return CurrentObject.Friction;
}
else
{
return 0.0f;
}
}
set
{
if (CurrentObject != null)
{
CurrentObject.Friction = value;
FirePropertyChanged("Friction");
}
}
}
public PlatformObjectTemplate CurrentObject
{
get
{
return GetValue(CurrentObjectProperty) as PlatformObjectTemplate;
}
set
{
SetValue(CurrentObjectProperty, value);
FirePropertyChanged("Friction");
FirePropertyChanged("CurrentObject");
BindShapes();
IntersectionComboBox.SelectedItem = CurrentObject.IntersectionStaticMethod;
}
}
public static readonly DependencyProperty CurrentObjectProperty = DependencyProperty.Register("CurrentObject", typeof(PlatformObjectTemplate), typeof(PlatformStaticObjectPropertyEditor), new PropertyMetadata(null));
My WPF control implements INotifyPropertyChanged, and my PlatformObjectTemplate does not have properties, just public fields like Friction. I need to bind to my object in XAML as such:
(in my control): //DoubleUpDown is from the WPF toolkit.
<tk:DoubleUpDown Margin="91,10,7,0" Name="doubleUpDown1" VerticalAlignment="Top" Value="{Binding Friction, ElementName=window, FallbackValue=0}" />
(in my main window):
<my:PlatformStaticObjectPropertyEditor x:Name="platformStaticObjectPropertyEditor1" CurrentObject="{Binding CurrentObject, ElementName=window}" />
I put a breakpoint in the getter of Friction property, and it tries to bind before the CurrentObject is bound, and because it is null, I can't read the correct friction value from the object. I've tried to fire Friction property changed in the setter of the CurrentObject, to populate the Friction when CurrentObject gets set, but that doesn't work either.
Ok, here are two requirements:
PlatformObjectTemplate will not use properties. It will have public fields.
I need a declarative way of binding as usual, just as I used in the XAML above.
I probably have got things over-complicated, and I must be missing some stuff. What is the most "correct" and "declarative" way of doing this right, within the constraints of my requirements just above?
Thanks,
Can.
object neither implements INotifyPropertyChanged nor inherits DependencyObject, and it will never. However, I need a mechanism to "bind" to fields of this object in my WPF control
poyra, I have this same situation. Because one cannot bind to instance fields, your best option is to create wrapper classes which implement INotifyPropertyChanged.

Triggering an animation from an event using MVVM

I seem to have reached some sort of MVVM breaking point here.
I would like for a control to have its opacity animated for half a second (DoubleAnimation from 0.5 to 1.0) when the underlying view model object have its "Status" property changed. I achieved this at first using a DataTrigger but since I haven't found a way to react to ANY change, just a given value, I had to always flip the VM objects "Status" property to a special "pending" value before setting it to its intended value. (Is there a way to react to ANY change btw?)
This was hacky so I started fiddling with EventTriggers instead...
This is what I've tried so far:
Using a normal EventTrigger
This seems to require a RoutedEvent but that, in turn, requires that my underlying view model object inherits from DependencyObject.
Using i:Interaction.Triggers
That way I can listen to and react to normal .NET events but I haven't found a way to start a StoryBoard using that approach.
Using i:Interaction.Triggers and writing a Behavior
This experiment fell short on the fact I found no way to attach my custom behavior to its associated control.
This is what the XAML looked like:
<cc:MyControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Updated">
<i:Interaction.Behaviors>
<cv:OpacityBehavior Duration="0:0:0:5" />
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
And here's the custom behavior:
class OpacityBehavior : Behavior<MyControl>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
}
That didn't work because the XAML parser required it to be attached directly to "MyControl" but I need to attach it to the event trigger. I then tried this approach:
class OpacityBehavior : Behavior<DependencyObject>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
private UIElement lookupVisualParent(DependencyObject dObj)
{
if (dObj is UIElement)
return (UIElement) dObj;
if (dObj == null)
return null;
return lookupVisualParent(LogicalTreeHelper.GetParent(dObj));
}
}
This failed on the fact that lookupVisualParent doesn't work. The logical parent of the behavior is always null.
It strikes me this should be a fairly common task? Is there a good solution to this problem? I find it strange that I will have write my view model classes so that they derive from DependencyObject in order to start an animation when an event fires.
Cheers
You could simply use a flag: set a flag on your VM called 'RecentlyChangedFlag'. Pulse it to true, then false, whenever the appropriate value(s) change. You could do that like this:
private bool _changedFlag;
public bool ChangedFlag
{
get
{
if (_changedFlag)
{
_changedFlag = false;
OnPropertyChanged("ChangedFlag");
return true;
}
// (else...)
return false;
}
protected set
{
_changedFlag = value;
OnPropertyChanged("ChangedFlag");
}
}
I.e., with the above code set ChangedFlag = true when you want to signal the animation to start. It will be reset to false after WPF queries the true value.
Then have the animation occur when the value of RecentlyChangedFlag is true, as an EnterAction for instance.
Hope that helps.

MvvM with custom View-Elements. Data Binding problems

As a newbie in Data-Binding, I don't know what I am doing wrong.
I have some GUI elements defined in XAML, and I have data-binded them with appropriate ViewModels. So far so good.
I also have some custom elements (geometrical shapes) that I place inside a Canvas (which Canvas I place inside the mainwindow through a user-control). I derived these entities from FrameworkElement, to have support for data-binding.
So what I have done is to register some DependencyProperties and set the bindings to one of the existing ViewModels, as it seemed to me logical.
Now the DependencyProperties of these custom classes, display some strange behaviour.
i) When I keep the focus only on the Views (controls) that use the same ViewModel with the custom elements, the properties update normally. If I click everywhere else, the bindings break.
ii) Sometimes, the Callback wasn't called although the property was changing.
iii) When the StartupURI in App.xaml was the MainWindow, if I declared the ProfileV as a field (no matter where it was instantiated), the databinding mechanism worked in the way of (i). If it was declared inside the constructor, the mechanism didn't worked.
What I am doing wrong, and what crucial thing i misunderstand about databinding ??
class ProfileV : FrameworkElement, IGraphicalElement
{
public int SelectedTab
{
get { return (int)GetValue(SelectedTabProperty); }
set { SetValue(SelectedTabProperty, value); }
}
public static readonly DependencyProperty SelectedTabProperty =
DependencyProperty.Register("SelectedTab", typeof(int), typeof(ProfileV),
new PropertyMetadata(new PropertyChangedCallback(CallBack)));
public ProfileV(GeneralExecutionVM VM,CanvasV canvasV)
{
DataContext = VM;
BindingOperations.SetBinding(this, SelectedTabProperty, new Binding("SelectedTab"));
}
public static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Which binds with this
public class GeneralExecutionVM : ObservableObject
{
private int _SelectedTab;
public int SelectedTab
{
get { return _SelectedTab; }
set
{
if (_SelectedTab == value) return;
_SelectedTab = value;
base.RaisePropertyChanged("SelectedTab");
}
}
}
(Observable Object, is the base class from the MVVM Foundation, of Josh Smith.)
ANSWERED
OK i found it. The misconception here is about the DataContext.
Be careful when and where you set it, against setting an explicit source object. I misused it here, and caused a small chaotic situation.

Resources