Run two Commands and Animation on Button Click - c#

In my app, I have a Left Grid and a Right Grid. Initially, the Left Grid is fully expanded (GridLength = 7*) while the Right Grid is not visible (Width = 0*).
If a Button in the LeftGrid is clicked and the Right Grid is NOT expanded, the Right Grid should expand to a Width of 3*.
If the Right Grid is expanded and a Button in the LeftGrid is clicked twice successively, then the RightGrid should shrink back to a Width of 0*.
These expansions/contractions should be Animated.
When a Button is clicked, three things must happen.
1) The id of the selected Button should be stored in a Property in the ViewModel.
2) The Width to which the RightGrid will be set, is stored in a Property in the ViewModel. This Command takes care of the two successive click case.
3) Finally, the Animation should run.
I am experiencing a few issues:
1) How do I bind the two Commands to a single Button?
<Button DockPanel.Dock="Top"
Command="{Binding DataContext.SetSelectedItemCommand,
RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding id}"
Command="{Binding ElementName=root, Path=DataContext.ChangeRightGridWidthCommand}"
>
This is not allowed.
I've been looking at this article (http://www.codeproject.com/Articles/25808/Aggregating-WPF-Commands-with-CommandGroup).
However, I'm not certain if this will work for me as I am defining my Commands in my ViewModel.
2) I am using a Custom GridLength Animation class as I need to use * for my Widths.
public class GridLengthAnimation : AnimationTimeline
{
public static readonly DependencyProperty ToProperty =
DependencyProperty.Register(
"To", typeof(GridLength), typeof(GridLengthAnimation));
public GridLength To
{
get { return (GridLength)GetValue(ToProperty); }
set { SetValue(ToProperty, value); }
}
public override Type TargetPropertyType
{
get { return typeof(GridLength); }
}
protected override Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
public override object GetCurrentValue(
object defaultOriginValue, object defaultDestinationValue,
AnimationClock animationClock)
{
return new GridLength(To.Value,
GridUnitType.Star);
}
}
I plan on using something like this in my XAML:
<proj:GridLengthAnimation
Storyboard.TargetName="rightGrid"
Storyboard.TargetProperty="Width"
To="RightGridWidth" Duration="0:0:2" />
<Grid.ColumnDefinitions>
<ColumnDefinition Name="leftGrid" Width="7*"/>
<ColumnDefinition Name="rightGrid" Width="{Binding RightGridWidth}"/>
</Grid.ColumnDefinitions>
Should I put the above XAML code within:
<EventTrigger RoutedEvent="Button.Click">
If so, will my Commands or the EventTrigger be run first? I need my Commands to be run first as only then will the To value have been set correctly when the Storyboard runs.
I'm quite confused as to how to put all this together.
Any help/advice would be greatly appreciated!!

Quick and dirty with what you already have, i.e. the EventTrigger that runs the animation.
Add a RightGridWidth property (with change notification) to your view model:
public GridLength RightGridWidth
{
get { return rightGridWidth; }
set
{
rightGridWidth = value;
OnPropertyChanged("RightGridWidth");
}
}
Toggle the property value in the SetSelectedItemCommand's execute handler:
if (RightGridWidth.Value == 0d)
{
RightGridWidth = new GridLength(3d, GridUnitType.Star);
}
else
{
RightGridWidth = new GridLength(0d, GridUnitType.Star);
}
Run the animation in the Click EventTrigger:
<Button Content="Click" Command="{Binding SetSelectedItemCommand}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<local:GridLengthAnimation
Storyboard.Target="{Binding ElementName=col2}"
Storyboard.TargetProperty="Width"
Duration="0:0:1" To="{Binding RightGridWidth}"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Finally, use a working implementation of the animation's GetCurrentValue, which has to return a value that depends on the CurrentProgress:
public override object GetCurrentValue(
object defaultOriginValue, object defaultDestinationValue,
AnimationClock animationClock)
{
var from = (GridLength)defaultOriginValue;
if (from.GridUnitType != To.GridUnitType ||
!animationClock.CurrentProgress.HasValue)
{
return from;
}
var p = animationClock.CurrentProgress.Value;
return new GridLength(
(1d - p) * from.Value + p * To.Value,
from.GridUnitType);
}
An entirely different and perhaps better approach would be the use of visual states. See VisualStateManager.

Related

Usercontrol - notify property change from ViewModel

I'm trying to implement custom Border, which binds to bool property in ViewModel, and whenever this property changes I want to do some animation with Border.
ViewModel property has OnPropertyChanged interface, It looks like this:
public bool Enable_control
{
get { return _enable_ctl; }
set { _enable_ctl = value; OnPropertyChanged(); }
}
private bool _enable_ctl;
This is how I bind custom Border in xaml:
<cust_border:Border_slide ShowSlide={Binding Enable_control}"/>
And this is my custom border control code:
public class Border_slide : Border
{
public Border_slide()
{
}
public bool ShowSlide
{
get { return (bool)GetValue(ShowSlideProperty); }
set { SetValue(ShowSlideProperty, value);}
}
public static readonly DependencyProperty ShowSlideProperty =
DependencyProperty.Register("ShowSlide", typeof(bool), typeof(Border_slide), new
FrameworkPropertyMetadata(true, new PropertyChangedCallback(ShowSlideChanged)));
private static void ShowSlideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//I would like to do animation of control when property of ViewModel changes - presumably here,
//but PropertyChangedCallback gets triggered only once - not on every Enable_control change!
}
}
So, question is: how do you properly update UserControl's dependency property from Viewmodel property change, in order to do something with UserControl next?
There was a time when I did something similar to yours.
In my case, I hope it will help since I solved it this way. But it can be different from what you think, so please let me know if there's anything I missed!
And I have uploaded my sample source code to GitHub.
Here sample sourcecode. https://github.com/ncoresoftsource/stackoverflowsample/tree/main/src/answers/dependency-border-animation
ViewModel
public class MainViewModel : ObservableObject
{
private bool _enable_ctl;
public bool Enable_control
{
get { return _enable_ctl; }
set { _enable_ctl = value; OnPropertyChanged(); }
}
}
MainWindow
I used Label instead of Border. Because Border cannot use ControlTemplate, it can be constrained to use and expand animation.
<CheckBox Content="Switch" IsChecked="{Binding Enable_control}"/>
<Label Style="{StaticResource SLIDER}"/>
App.xaml
<Style TargetType="Label" x:Key="SLIDER">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="#AAAAAA"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border x:Name="border" Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Opacity="0">
<ContentPresenter/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Enable_control}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="1" Duration="00:00:0.5"
Storyboard.TargetName="border"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="1" To="0" Duration="00:00:0.5"
Storyboard.TargetName="border"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Solved. Clemens was partially right - nothing was wrong with my code, property did get notified with changes. My problems were elsewhere - with a variable that didn't get value before property changed, and default value of dependency property. I sorted out those things now, and It works as expected.
Here is my full code, might get useful to someone, at least I find It so:
It's a custom Border, which can be used for a sliding animation of left Margin. Meaning you can use this Border to slide some menues to View from left side to desired right location, just add controls inside It and you're ready to go. Code can be used for other controls too (Panel etc.), but I used Border since I can round corners or do other nice UI presentations with it easily.
Logic behind is that If you set MoveTo property, Border will slide to desired left Margin location. If you don't, then Border will slide to center of screen - meaning you can use this Border for sliding menues into Views that are centered or left aligned in yout MainWindow. That was actually whole point of creating It - because you cannot use dynamic values in animation To or From, since these are freezables. So I had to create this custom control, because I couldn't do an animation to screen center (width needs to be calculated first). Anyway, here It is:
public class Border_slide : Border
{
public Border_slide()
{
Loaded += Border_slide_Loaded;
}
private void Border_slide_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
//Save starting Margin.Left
StartX = Margin.Left;
}
///<summary>Property for starting X location</summary>
private double StartX
{
get { return _startX; }
set { _startX = value; }
}
private double _startX;
public static DependencyProperty MoveToProperty =
DependencyProperty.Register("MoveTo", typeof(double), typeof(Border_slide), new PropertyMetadata(default(double)));
///<summary>Property thats sets to what Margin.Left we want move Border</summary>
public double MoveTo
{
get { return (double)GetValue(MoveToProperty); }
set { SetValue(MoveToProperty, value); }
}
public bool ShowSlide
{
get { return (bool)GetValue(ShowSlideProperty); }
set { SetValue(ShowSlideProperty, value); }
}
public static readonly DependencyProperty ShowSlideProperty =
DependencyProperty.Register("ShowSlide", typeof(bool), typeof(Border_slide), new FrameworkPropertyMetadata(true,
new PropertyChangedCallback(ShowSlideChanged)));
private static void ShowSlideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var brd = d as Border_slide;
//Animate Margin, when Property changes
if (((bool)e.NewValue) == true) //When property is true, Border is allready at desired location, so we move It to first x- location
{
ThicknessAnimation back_to_start_X_location = new ThicknessAnimation
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(1),
From = new Thickness(brd.Margin.Left, 0, 0, 0),
To = new Thickness(brd.StartX, 0, 0, 0)
};
brd.BeginAnimation(Border.MarginProperty, back_to_start_X_location);
}
else //If property is False we need to move Border to desired X- location
{
//If we don't set MoveTo property then move Border to center of screen
if (brd.MoveTo == default(double))
{
var X_center = Application.Current.MainWindow.ActualWidth / 2 - brd.Width / 2;
ThicknessAnimation to_center_X_location = new ThicknessAnimation
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(1),
From = new Thickness(brd.ZacetniX, 0, 0, 0),
To = new Thickness(X_center, 0, 0, 0)
};
brd.BeginAnimation(Border.MarginProperty, to_center_X_location);
}
else //If MoveTo property is set then move Border to desired X-location
{
ThicknessAnimation to_desired_X_location = new ThicknessAnimation
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(1),
From = new Thickness(brd.StartX, 0, 0, 0),
To = new Thickness(brd.MoveTo, 0, 0, 0)
};
brd.BeginAnimation(Border.MarginProperty, to_desired_X_location);
}
}
}
}
So, as you see - Border moves depending on what you set as start Margin.Left (that is a must) and/or MoveTo property, with binding to some bool value from ViewModel. In my case, I slide this Border when I disable all my other controls in View - so that I can make blur effect on View too. If you will use It for different things, than be careful about default value of ShowSlide property (mine is true). And surely you can re-adjust control for different things too...
E.g. usage in XAML:
<my_controls:Border_slide Margin="-500,0,0,0" MoveTo="5" ShowSlide="{Binding Enable_control}">

How to animate the Foreground property of multiple "Run" elements in a TextBlock in UWP?

I have this code. This is just an example. I want to do it with code. How to animate the Foreground property of multiple "Run" elements in a TextBlock?
<Page
x:Class="AnimationTest.MainPage"
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:local="using:AnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<TextBlock
x:Name="_textBlockElement"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="72"><Run x:Name="_run1" Text="Hel" /><Run Text="lo" />
<TextBlock.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard x:Name="ColorStoryboard">
<ColorAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetName="_textBlockElement"
Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
To="Red"
Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Grid>
</Page>
Oke, so this took quite some sorting out, but turned out to be quite doable.
The key is to create two storyboards in code behind with the correct animations and then add those storyboards to the resources of any parent of the Run's.
Let's start of with the XAML code, which is pretty simple:
<Grid>
<TextBlock x:Name="TestBlock"
HorizontalAlignment="Center" VerticalAlignment="Center"
PointerEntered="TestBlock_PointerEntered"
PointerExited="TestBlock_PointerExited">
<Run x:Name="Run1" Text="Test1" Foreground="Blue"/>
<Run x:Name="Run2" Text="Test2" Foreground="Green"/>
<!-- ... -->
</TextBlock>
</Grid>
For simplicity I have already defined the names and foregrounds of the Run's.
Now we need to define the storyboards and animations in code behind.
I've chosen to do this in the constructor (after InitializeComponent()!). In theory you should be able to also paste this code in the Page_Loaded event.
public MainPage()
{
InitializeComponent();
SetupStoryBoards();
}
void SetupStoryBoards()
{
// Define duration and storyboards to red and original color
var duration = new Duration(TimeSpan.FromSeconds(1));
var toRedStory = new Storyboard { Duration = duration };
// completed events can be subscribed to, to register when animation is done
//toRedStory.Completed += Story_Completed;
var toOriginalStory = new Storyboard { Duration = duration };
//toOriginalStory.Completed += ToOriginalStory_Completed;
foreach (Run r in TestBlock.Inlines)
{
// Filter out any inlines that are not a named Run
if (string.IsNullOrEmpty(r.Name))
continue;
// Define the animations
var toRedAnim = new ColorAnimation
{
Duration = duration,
To = Colors.Red,
EnableDependentAnimation = true
};
var toOriginalAnim = new ColorAnimation
{
Duration = duration,
To = (r.Foreground as SolidColorBrush).Color, // Causes animation to go back to original foreground color of Run
EnableDependentAnimation = true
};
// Add animations to the storyboards and associate animations with the Run
toRedStory.Children.Add(toRedAnim);
toOriginalStory.Children.Add(toOriginalAnim);
Storyboard.SetTargetName(toRedAnim, r.Name);
Storyboard.SetTargetName(toOriginalAnim, r.Name);
Storyboard.SetTargetProperty(toRedAnim, "(Run.Foreground).(SolidColorBrush.Color)");
Storyboard.SetTargetProperty(toOriginalAnim, "(Run.Foreground).(SolidColorBrush.Color)");
}
// Add the storyboards to the resources of any parent of the Run's for easy retrieval later and to make the animations find the Run's
// I choose the resources of the textblock that contains the Run's
TestBlock.Resources.Add("toRedStory", toRedStory);
TestBlock.Resources.Add("toOriginalStory", toOriginalStory);
}
Now to execute the animations, we add the PointerEntered and PointerExited eventhandlers, and begin the correct storyboards there:
private void TextBlock_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var story = TestBlock.Resources["toRedStory"] as Storyboard;
story.Begin();
}
private void TextBlock_PointerExited(object sender, PointerRoutedEventArgs e)
{
var story = TestBlock.Resources["toOriginalStory"] as Storyboard;
story.Begin();
}
You should be able to extend this wherever needed, however I have found that EnableDependentAnimation must be set to true since otherwise it won't work.

MVVM Navigation Switching ViewModels causes an error with animations

I am trying to make a WPF application using the MVVM pattern.
I now want to be able to navigate between ViewModels, and for that I've used this article: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Basically, the Main Window Resources contain DataTemplates for each View Model linking it to it's view, and a Content Control bound to CurrentPageViewModel like this:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModels:HomeViewModel}">
<Views:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:VaultViewModel}">
<Views:VaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:VaultsViewModel}">
<Views:VaultsView />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}" />
In the constructor of the Main Window I set CurrentPageViewModel to a new instance of my home screen view model and when I want to navigate, all I need to do is change CurrentPageViewModel and WPF will do the rest because of my use of INotifyPropertyChanged.
I am sending Change Page requests to the main window from my View Models by copying the code from MVVM Light's messenger implementing the Mediator pattern, and this works fine, except when I have Storyboards playing.
I have two very nice buttons inside of my home page, which will grow a bit when you hover over them:
<Button Style="{DynamicResource MaterialButton}" Width="400px" Height="400px" Margin="15px"
FontSize="24" FontFamily="Roboto" FontWeight="Bold"
Effect="{DynamicResource HomeButtonDropShadowEffect}" Command="{Binding DisplayVaultsView}"
Content="Vaults">
<!--#region button effects -->
<Button.RenderTransform>
<ScaleTransform x:Name="scaleTransform" CenterX="200" CenterY="200" ScaleX="1.0" ScaleY="1.0"/>
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleX"
To="1.08" Duration="0:0:0.1"/>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleY"
To="1.08" Duration="0:0:0.1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleX"
To="1.0" Duration="0:0:0.1" FillBehavior="Stop"/>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleY"
To="1.0" Duration="0:0:0.1" FillBehavior="Stop"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
<!--#endregion-->
</Button>
As you can see, the button's Command is set to DisplayVaultsView, which is a method inside of the HomeViewModel which sends the Change Page request to the Main Window.
Without these storyboards inside the button, this works just fine and the new page is displayed. However, with the storyboards, the following error is thrown once I click the button:
System.InvalidOperationException: ''scaleTransform' name cannot be found in the name scope of 'System.Windows.Controls.Button'.'
This has led me to believe that the animation is still attempting to play, but since the Button is not in the current context anymore it can't find the property it's supposed to animate.
It's also important to note that the HomeViewModel is not thrown away when you navigate to another page, I've written my own Navigator class which keeps track of the ViewModels so you can navigate forwards/backwards without losing any changes (and without having to reload everything).
I'd like to know how I can solve this error, but it has also led me to the realization that I might need to "unload" ViewModels when switching to another one. I'm currently just keeping all ViewModel references inside of a List inside the Navigator class. So my other question is, should I "unload" the ViewModels when switching to another, and how?
I found a solution which works, but I'm not sure as to why it worked.
I removed all of the XAML concerned with the animations, and moved the animating to the code-behind in HomeView.xaml.
Here's the code:
public partial class HomeView : UserControl
{
private Storyboard _mouseEnterStoryboard;
private Storyboard _mouseLeaveStoryboard;
private DoubleAnimation _scaleX_In;
private DoubleAnimation _scaleY_In;
private DoubleAnimation _scaleX_Out;
private DoubleAnimation _scaleY_Out;
public HomeView()
{
InitializeComponent();
_mouseEnterStoryboard = new Storyboard();
ScaleTransform scaleVaultsButton = new ScaleTransform(1.0, 1.0);
ScaleTransform scaleFilesButton = new ScaleTransform(1.0, 1.0);
VaultsButton.RenderTransformOrigin = new Point(0.5, 0.5);
VaultsButton.RenderTransform = scaleVaultsButton;
FilesButton.RenderTransformOrigin = new Point(0.5, 0.5);
FilesButton.RenderTransform = scaleFilesButton;
_scaleX_In = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.0,
To = 1.08
};
_scaleY_In = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.0,
To = 1.08
};
_mouseEnterStoryboard.Children.Add(_scaleX_In);
_mouseEnterStoryboard.Children.Add(_scaleY_In);
Storyboard.SetTargetProperty(_scaleX_In, new PropertyPath("RenderTransform.ScaleX"));
Storyboard.SetTargetProperty(_scaleY_In, new PropertyPath("RenderTransform.ScaleY"));
_mouseLeaveStoryboard = new Storyboard();
_scaleX_Out = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.08,
To = 1.0
};
_scaleY_Out = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.08,
To = 1.0
};
_mouseLeaveStoryboard.Children.Add(_scaleX_Out);
_mouseLeaveStoryboard.Children.Add(_scaleY_Out);
Storyboard.SetTargetProperty(_scaleX_Out, new PropertyPath("RenderTransform.ScaleX"));
Storyboard.SetTargetProperty(_scaleY_Out, new PropertyPath("RenderTransform.ScaleY"));
VaultsButton.MouseEnter += new MouseEventHandler(Button_MouseEnter);
VaultsButton.MouseLeave += new MouseEventHandler(Button_MouseLeave);
FilesButton.MouseEnter += new MouseEventHandler(Button_MouseEnter);
FilesButton.MouseLeave += new MouseEventHandler(Button_MouseLeave);
}
public void Button_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard.SetTarget(_scaleX_In, (Button)sender);
Storyboard.SetTarget(_scaleY_In, (Button)sender);
_mouseEnterStoryboard.Begin();
}
public void Button_MouseLeave(object sender, MouseEventArgs e)
{
Storyboard.SetTarget(_scaleX_Out, (Button)sender);
Storyboard.SetTarget(_scaleY_Out, (Button)sender);
_mouseLeaveStoryboard.Begin();
}
}
I'm not sure as to why this does work and the XAML version doesn't, so I'd still like an answer to that question.

WPF Event Trigger Add BlurEffect

I want to add a blur effect to my window in some cases so I wrote a custom window style.
-I wrote a custom style because I have buttons in front of the blur effect that will get visible when the blur effect is applied, but the buttons should not get blurred.
I used the following code to apply blur:
<AdornerDecorator.Effect>
<BlurEffect Radius="{Binding Path=(local:StandardWindowEventHandler.LockedOverlayVisibility),
Converter={StaticResource VisibilityToBlurConverter}}"
KernelType="Gaussian" />
</AdornerDecorator.Effect>
this worked fine but the GPU is at ~50% when I'm in my TreeView even when radius is 0.
Without blur effect it is at like 2%. Now I don't want to set the radius any more, but the whole blur effect instead.
I have tried this:
<AdornerDecorator.Triggers>
<DataTrigger Binding="{Binding Path=(local:StandardWindowEventHandler.LockedOverlayVisibility)}"
Value="Visible">
<Setter TargetName="PART_WindowAdornerDecorator"
Property="Effect">
<Setter.Value>
<BlurEffect Radius="10" />
</Setter.Value>
</Setter>
</DataTrigger>
</AdornerDecorator.Triggers>
Unfortunately Triggers shall be Event triggers. If I make the event trigger change the radius I've won nothing, is there any possibility to add the blur effect via event trigger?
Thanks in advance
I've found a solution:
<Grid x:Name="Root_Grid"
VerticalAlignment="Stretch">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click"
SourceName="Button1">
<window:SetBlurEffectAction />
</i:EventTrigger>
<i:EventTrigger EventName="Click"
SourceName="Button2">
<window:SetBlurEffectAction />
</i:EventTrigger>
</i:Interaction.Triggers>
<AdornerDecorator ClipToBounds="True"
x:Name="PART_WindowAdornerDecorator">
<ContentPresenter x:Name="PART_RootContentPresenter"
ContentTemplate="{TemplateBinding ActualWindowTemplate}"
dxr:RibbonControlHelper.IsAutoHide="{TemplateBinding RibbonAutoHideMode}"
dxr:RibbonControlHelper.DisplayShowModeSelector="{TemplateBinding DisplayShowModeSelector}" />
</AdornerDecorator>
The Root_Grid is the parent of the AdornerDecorator as you can see.
Button1 and Button2 are some grandchildren of the Root_Grid.
public class SetBlurEffectAction : TriggerAction<DependencyObject> {
protected override void Invoke(object parameter) {
var e = parameter as RoutedEventArgs;
// OriginalSource is one of the buttons
var parent = e?.OriginalSource as UIElement;
// get Grid (parent of AdornerDecorator)
while(true) {
if(parent == null) {
return;
}
var grid = parent as Grid;
if("Root_Grid".Equals(grid?.Name)) {
break;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
// apply blur to this AdornerDecorator
var deco = ((Grid)parent).GetElementByName("PART_WindowAdornerDecorator") as AdornerDecorator;
if(deco == null) {
return;
}
// != collapsed because the property is not updated yet
if(StandardWindowEventHandler.LockedOverlayVisibility != Visibility.Collapsed) {
deco.Effect = null;
} else {
deco.Effect = new BlurEffect {
KernelType = KernelType.Gaussian,
Radius = 7
};
}
}
}
StandardWindowEventHandler.LockedOverlayVisibility is a static Property that gets updated when one of the buttons is pressed.

Setting the image position via code

I want to set the image position via code to fit on the button (see screenshot). But I can't work it out. In WinForms it was easy, but in Silverlight I can't just set the X & Y apparently.
public void LockControls()
{
int LockIndex = 0;
DependencyObject myUserControl = LayoutRoot;
foreach (var button in FindAll<Button>(myUserControl))
{
if (button.Tag != null)
{
Image LockedIcon = new Image();
LockedIcon.Width = 20;
LockedIcon.Height = 20;
//LockedIcon.Margin = new Thickness(0,0,0,0);
LockedIcon.Source = new BitmapImage(new Uri("images/LockedIconx20alpha.png", UriKind.Relative));
LockedIcon.Name = "Lockie" + LockIndex;
LayoutRoot.Children.Add(LockedIcon);
button.Tag = "Locked" + LockIndex;
LockIndex++;
}
}
}
http://puu.sh/wS7g
THe screenshot shows the image position (the locck), but I don't understand how the current position is being set. Just to clarify, I want to set the position to the "0%" button
Thanks in advance,
Jack
That's not the correct way of doing. If I understand correctly you want to super-impose an image on top of a button to prevent the user from using it. It won't work.
For that you have to understand the layout system of Silverlight: the controls are laid out by the engine during the measure and arrange events.
Trying to overlay an image like that will require you to hook up on those events, or derive the Button class and override the Arrange method to overlay your image.
But that won't prevent the user from using the button because the button itself is not disabled, and one could just "tab" into it, and activate it.
Instead I suggest you use a style for the button, and override, say, the Disabled state to overlay your locked image. The button style is described here.
All you have to do is replace:
<Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
by
<Image x:Name="DisabledVisualElement" IsHitTestVisible="false" Opacity="0" Width="20" Height="20" Source="images/LockedIconx20alpha.png" />
And set the opacity in the following to 1:
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
</Storyboard>
</vsm:VisualState>
Set this style on your button (I assume you know how to do this) then when you need to lock your button, set it to disabled and your image will be automatically laid on-top and your button will be un-clickable.
In general, if you want to set the Image position arbitrary, you should host the Image control in a Canvas container.
However, in your case you should really change the Content element of your Button depending on the need for showing the lock or not.
<UserControl DataContext="{Binding Main, Source={StaticResource Locator}}
<Grid.Resources>
<converters:VisibilityConverter x:Key="VisibilityConverter" />
</Grid.Resources>
<Button Width="100" Height="23" IsEnabled="{Binding IsControlsEnabled}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="lock.png" Margin="10,0,10,0"
Visibility="{Binding IsControlsEnabled, Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Button"/>
</StackPanel>
</Button.Content>
</Button>
Additionally, you shouldn't really write the kind of code you have in your question in Silverlight. Learn how to use data binding. It's very powerful. Simply bind the IsEnabled property of your Buttons to an exposed Property instead.
An example of doing so using the MVVM Light toolkit (I recommend you learn the MVVM pattern for Silverlight/WPF development):
The View Model:
public class MainViewModel : ViewModelBase
{
private bool isControlsEnabled;
public bool IsControlsEnabled
{
get { return isControlsEnabled; }
set
{
if (IsControlsEnabled.Equals(value)) return;
isControlsEnabled = value;
RaisePropertyChanged(() => IsControlsEnabled);
}
}
}
The Visibility Converter:
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then you can simply enable/disable all controls that are bound, for example using a CheckBox:
<CheckBox IsChecked="{Binding IsControlsEnabled, Mode=TwoWay}" Content="Controls are enabled"/>

Categories