I have my custom toolbar control with DependencyProperty IsBusy
Here is how I use it:
<Controls:myToolbar
Grid.ColumnSpan="5" Mode="DataEntry"
Status="{Binding State, Converter={StaticResource ViewEditingStateToToolbarStateConverter}}"
IsBusy="{Binding IsBusy}"/>
By convention all my VM's inherit from base VM and have IsBusy property.
So, I KNOW that this property will always be available on VM.
Now I have another 4 properties like this. Instead of adding them to XAML on all my views I want to know how to bind to this IsBusy automatically inside control's code so I don't have to bind in XAML?
EDIT
Actually, I found answer to my question: Silverlight: Programmatically binding control properties
Now, my question is:
Is it correct to apply this binding in constructor like this?
public myToolbar()
{
this.DefaultStyleKey = typeof(myToolbar);
var binding = new Binding("IsBusy") { Mode = BindingMode.TwoWay };
this.SetBinding(IsBusyProperty, binding);
}
Should I check if XAML binding (another binding) exist to this property and not bind? It works either way but I wonder if it's bad for performance, smells, etc?
What about doing this in onApplyTemplate. Is that better way?
if (GetBindingExpression(IsBusyProperty) == null)
{
var binding = new Binding("IsBusy") { Mode = BindingMode.TwoWay };
this.SetBinding(IsBusyProperty, binding);
}
It would be bad if you tried to use this control with a view model which doesn't have the IsBusy property, but even then you'll receive just a debug warning in the output window, nothing to worry about.
As to the place of the binding, the constructor is appropriate if the dependency property which you are binding to doesn't perform any actions inside its callback.
But if the property changed callback tries to call such functions as GetTemplateChild and retrieve inner controls - then you should move the binding to the OnApplyTemplate functions, because only there you can be assured that inner controls exist.
By the way, if your dependency proeprty doesn't have a property changed callback and is used only in the control template like {TemplateBinding IsBusy}, you can replace this line by {Binding IsBusy}. Something like this, either by using binding or data triggers:
<ControlTemplate TargetType="{x:Type Controls:myToolbar}">
<Grid>
<ContentControl x:Name="content" ... />
<ProgressBar x:name="progress" ... />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<Setter TargetName="progress" Property="Visibility" Value="Visible" />
</DataTrigger>
The idea is simple: TemplateBinding is applied to dependency properties of the control, whereas Binding is applied to properties of the DataContext object or the view model and they can coexist without problems.
Related
I'm making first steps in learning Xamarin Forms and run into design question which can't find easy solution for. I want to utilize MVVM approach and implement it with best practices in mind, though I realize same thing could be done in different ways.
So I have Button and Label controls in View. Clicking button sends Command to ViewModel that runs some calculations (or delegates them to the model) and impacts text and visual representation of the Label in the text field. ViewModel has Text property to display in the textbox which could easily to be done with property bindings
<Label Text="Binding Text" ...></Label>
so once ViewModel modifies the text it's shown on a View updated, it's not a problem. The design in question is second part: I'm not sure how to update visual styling for the label, like border color and strikethrough style of font right after calculation is done in ViewModel, it's something that I believe only codebehind (xaml.cs) could properly do, not binding.
One of the approach would be to leave it as a command xaml -> VM -> M
<Button ButtonClickedCommand="{Binding RunCalculationsCommand}" ...>
and have VM to raise a certain ad-hoc event in ViewModel that codebehind catches and does it "styling" job: VM -> xaml.cs
Another is to have a regular event (not Command) raised on view that starts calculation in ViewModel and runs styling xaml -> xaml.cs -> VM -> M
<Button Clicked="RunCalculations_OnClicked" ...>
But then it's more tightly coupled and does not fully follows MVVM per my understanding.
Or I guess the best one could be something else that I'm not yet aware of
Two options: bind a trigger, or bind the control properties directly to VM.
Then, update VM properties in your button command.
Bind a trigger with preset styles.
<Label Text="{Binding Text}" TextColor="Green">
<Label.Triggers>
<DataTrigger
Binding="{Binding CalculationResultIsNegative}"
TargetType="Label"
Value="true">
<!-- set Specific Property -->
<Setter Property="TextColor" Value="Red" />
<!-- or set Specific Style in App.xaml -->
<Setter Property="Style" Value="{StaticResource RedStyleDefinedInAppXaml}" />
</DataTrigger>
</Label.Triggers>
</Label>
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
CalculationResultIsNegative = true;
//else..
//CalculationResultIsNegative = false;
}
Bind control properties in XAML
<Label Text="{Binding Text}"
TextColor="{Binding ColorPropertyFromVM"}
FontAttributes="{Binding FontAttributesProperty"} />
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
ColorPropertyFromVM = Color.Red;
FontAttributesProperty = FontAttributes.Bold;
}
I am looking at a problem where my own created gauge control doesn't deal with bindings correctly during startup/creation. It works fine once user control and viewmodel are instantiated and all bindings are set.
I have the following control (all user code):
<linearGauge:LinearGaugeControl
Grid.Row="2"
Margin="30, 0, 0, 0"
GaugeLabel="Flow"
LinearGaugeLength="800"
LinearGaugeHeight="80"
LabelFontSize="20"
NeedleColor="Black"
Grid.Column="0" Grid.ColumnSpan="2"
DataContext="{Binding FlowGaugeData}" />
This question is about two properties in the xaml above:
LinearGaugeLength="800"
LinearGaugeHeight="80"
These properties are bound to the view model of the LinearGaugeControl :
<UserControl.Resources>
<Style TargetType="local:LinearGaugeControl">
<Setter Property="LinearGaugeLength" Value="{Binding GaugeSize, Mode=OneWayToSource}"/>
<Setter Property="LinearGaugeHeight" Value="{Binding BarThickness, Mode=OneWayToSource, NotifyOnSourceUpdated=True}"/>
</Style>
</UserControl.Resources>
From my previous struggle with DPs I learned something useful from user ASh, that I can add a callback to my DP so that I at least know that it gets triggered. That works fine. The callback of the LinearGaugeHeight DP fires with the 'new' value of 80 (old value=50).
After that event, my setter gets called, with the wrong value ! (50, the default value of the DP).
What is going wrong?
Is this going wrong because the view is created before the viewmodel is?
In reply to #Clemens:
We are using caliburn, can that in some way cause this problem? I think I read somewhere that caliburn uses DataContext=this under the hoods?
How to focus a textbox from ViewModel wpf?
<TextBox Name="PropertySearch"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Path=PropertySearch,
ValidatesOnDataErrors=True}"
Width="110"
Height="25"
Margin="10" />
You can do this by adding a property to your ViewModel (or use an existing property) that indicates when the SetFocus should happen but the View should be responsible for actually setting the focus since that is purely View related.
You can do this with a DataTrigger.
View:
<Grid Name="LayoutRoot" DataContext="{StaticResource MyViewModelInstance}">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding UserShouldEditValueNow}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=PropertySearch}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBox Name="PropertySearch" Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Path=PropertySearch, ValidatesOnDataErrors=True}" Width="110" Height="25" Margin="10" />
</Grid>
ViewModel:
// When you think the view should set focus on a control
this.UserShouldEditValueNow = true;
The example above is simplified by just using a boolean ViewModel property "UserShouldEditValueNow". You can add a property like this to your ViewModel or use some other exising property that indicates this state.
Note: So why is it done this way in MVVM? One reason is, suppose the View author decided to replace the TextBox with a ComboBox, or even better, suppose your property was an integer value that had both a TextBox to view/edit the number and a Slider as another way to edit the same value, both controls bound to the same property... how would the ViewModel know which control to set focus on? (when it shouldn't even know what control, or controls, are bound to it in the first place) This way the View can select which control to focus by changing the ElementName binding target in the DataTrigger Setter.
Happy coding!
The question you should be asking yourself is "why does my ViewModel need to know which control has the focus?"
I'd argue for focus being a view-only property; it's an interaction property, and has nothing to do with the conceptual state. This is akin to the background color of a control: why would you represent it in the VM? If you need to manage the focus in a custom way, it's probably better to use a view-level object to do the job.
In your parent control, add the following property:
FocusManager.FocusedElement="{Binding ElementName=PropertySearch}"
While purists may argue for leaving this out of the VM, there are cases where it may make sense to do so from the VM.
My approach has been to make the view implement an interface, pass that interface to the ViewModel, and then let the VM call methods on the interface.
Example:
public interface IFocusContainer
{
void SetFocus(string target);
}
A couple things to keep in mind:
A VM might serve more than one instance of a view, so your VM might want to have a collection of references to IFocusContainer instances, not just one.
Code the VM defensively. You don't know whether there are 0, 1 or 20 views listening.
The "target" parameter of SetFocus() should probably be "loosely" coupled to the VM. You don't want the VM caring about the exact control names in the UI. Rather, the VM should indicate a name that is defined solely for focus management. In my case, I created some attached properties that would allow me to "tag" controls with "focus names".
To implement the interface, you can:
Implement it in the code-behind
Create some behaviors that know how to attach to the ViewModel that is present in the DataContext.
There's nothing wrong with implementing it on the Code Behind, but the behavior approach does allow a XAML only hookup if that's important to you.
In the implementation of the interface, you can use the visual tree to locate the control, or you could just code up a switch statement for a known set of focusable items.
So, I have this Window with some controls. In the resources section I've defined this style:
<Style x:Key="StyleNavBar" TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentTheme, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<DataTrigger.Value>
<theme:WinTheme>WindowsClassic</theme:WinTheme>
</DataTrigger.Value>
<Setter Property="Background" Value="#FFFFFFFF" />
</DataTrigger>
</Style.Triggers>
</Style>
In my Window I define an attached property named 'CurrentTheme' which stores (based on an enum) the current theme used system-wide. Here's the code:
public static readonly DependencyProperty CurrentSystemThemeProperty =
DependencyProperty.RegisterAttached(
"CurrentSystemTheme",
typeof(WinTheme),
typeof(MainWindow),
new UIPropertyMetadata(WinTheme.AeroGlass));
public WinTheme CurrentTheme
{
get
{
return (WinTheme)GetValue(CurrentSystemThemeProperty);
}
set
{
SetValue(CurrentSystemThemeProperty, value);
}
}
Everytime the user changes the system theme, my window receives a callback via WndProc, informing that the theme has changed. And, as you can see, the default value of the CurrentTheme property is WinTheme.AeroGlass. Then I have a grid styled with the style defined above:
<Grid Height="34" Name="grdNavBar" VerticalAlignment="Top" Style="{DynamicResource StyleNavBar}">
My goal is to change the style based on the value of CurrentTheme, but the trigger defined above does not work when the theme changes (it only works with the default value of CurrentTheme, i.e., isn't reacting to changes in the property).
Any ideas on how to accomplish this?
You've passed a different name to DependencyProperty.RegisterAttached than the name you've given the property in C#. So the DP system thinks it's called CurrentSystemTheme, but your code thinks it's called CurrentTheme. Try passing CurrentTheme as the first argument to RegisterAttached.
Also, you might want to enable WPF debug log output for data binding (which is on by default in older versions of WPF, but in .NET 4/VS 2010, you need to go and switch it on in the Tools->Options window under Debugging->Output Window). That way, I usually set the WPF Trace Settings -> Data Binding option to All. That way if a data binding fails, you'll see an error in the Output window. This might help you diagnose why that data trigger is failing.
I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.