layered window with blur effect - c#

I really like the effect that can be seen for example in iOS, which basicaly looks like a layer drawn on top of current view , bluring the visual content and using that as a background. Is there a way to achieve something like that in WPF?
I've seen people mostly dealing with this blur/transparency on Window level, but I need it within the window.
Let's say this is the content of my window.
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Image Source="pack://application:,,,/Resources/Penguins.jpg"/>
<Image Source="pack://application:,,,/Resources/Penguins.jpg"/>
</StackPanel>
Which looks like
And now I'd like to draw something on top of that which ( instead of using red background ) blures whatever is beneath it and uses it as background, keeping it's content not blury.
<DockPanel Margin="15" Background="Red">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<Label Content="Some label"/>
<TextBox Width="100" Height="20"/>
</StackPanel>
</DockPanel>

Result:
We will use layering in a Grid. Background: Your main application content. Foreground: Your pseudo-dialog that will have a blurred background.
We will put the background in a border and refer to this border by its name. This will be used in a VisualBrush and provide our to-be-blurred image.
The foreground will also be a layered grid. Background: A rectangle, filled with the brush and using a blur effect. Foreground: Whatever you want to be in front.
Add a reference to System.Windows.Interactivity.
Add the following behavior code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
namespace WpfApplication1
{
public class BlurBackgroundBehavior : Behavior<Shape>
{
public static readonly DependencyProperty BlurContainerProperty
= DependencyProperty.Register(
"BlurContainer",
typeof (UIElement),
typeof (BlurBackgroundBehavior),
new PropertyMetadata(OnContainerChanged));
private static readonly DependencyProperty BrushProperty
= DependencyProperty.Register(
"Brush",
typeof (VisualBrush),
typeof (BlurBackgroundBehavior),
new PropertyMetadata());
private VisualBrush Brush
{
get { return (VisualBrush) this.GetValue(BrushProperty); }
set { this.SetValue(BrushProperty, value); }
}
public UIElement BlurContainer
{
get { return (UIElement) this.GetValue(BlurContainerProperty); }
set { this.SetValue(BlurContainerProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Effect = new BlurEffect
{
Radius = 80,
KernelType = KernelType.Gaussian,
RenderingBias = RenderingBias.Quality
};
this.AssociatedObject.SetBinding(Shape.FillProperty,
new Binding
{
Source = this,
Path = new PropertyPath(BrushProperty)
});
this.AssociatedObject.LayoutUpdated += (sender, args) => this.UpdateBounds();
this.UpdateBounds();
}
protected override void OnDetaching()
{
BindingOperations.ClearBinding(this.AssociatedObject, Border.BackgroundProperty);
}
private static void OnContainerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((BlurBackgroundBehavior) d).OnContainerChanged((UIElement) e.OldValue, (UIElement) e.NewValue);
}
private void OnContainerChanged(UIElement oldValue, UIElement newValue)
{
if (oldValue != null)
{
oldValue.LayoutUpdated -= this.OnContainerLayoutUpdated;
}
if (newValue != null)
{
this.Brush = new VisualBrush(newValue)
{
ViewboxUnits = BrushMappingMode.Absolute
};
newValue.LayoutUpdated += this.OnContainerLayoutUpdated;
this.UpdateBounds();
}
else
{
this.Brush = null;
}
}
private void OnContainerLayoutUpdated(object sender, EventArgs eventArgs)
{
this.UpdateBounds();
}
private void UpdateBounds()
{
if (this.AssociatedObject != null && this.BlurContainer != null && this.Brush != null)
{
Point difference = this.AssociatedObject.TranslatePoint(new Point(), this.BlurContainer);
this.Brush.Viewbox = new Rect(difference, this.AssociatedObject.RenderSize);
}
}
}
}
Use it in XAML like this:
<Grid>
<Border x:Name="content">
<Border.Background>
<ImageBrush ImageSource="bild1.jpg" />
</Border.Background>
<StackPanel>
<TextBox Width="200" Margin="10" />
<TextBox Width="200" Margin="10" />
<TextBox Width="200" Margin="10" />
</StackPanel>
</Border>
<Grid Margin="59,63,46,110">
<Rectangle ClipToBounds="True">
<i:Interaction.Behaviors>
<wpfApplication1:BlurBackgroundBehavior BlurContainer="{Binding ElementName=content}" />
</i:Interaction.Behaviors>
</Rectangle>
<TextBox VerticalAlignment="Center" Text="Blah" Width="200" Height="30" />
</Grid>
</Grid>

Related

How to edit an observableCollection while WPF app is running

I am making a To-do List application for one of my classes. Now I have tried in a console app to update objects (of TaskModels a type I defined) inside an observable collection and it has seemed to work. But now I am trying to update the entries inside the observable collection. I have wrote some code that I thought was going to change the value but it did not change the value in the ListBox I will include the code below for TaskModel.
Originally I was wanting to have the edit button open a new window that allowed the user to input what the wanted the task to be and then they press a button maybe called change task and it sends it back to the original window. But for simpleness at the moment I would like to change with the textbox in the user control in the main window.
I am also pretty new to making apps in WPF so this is all pretty new to me and I am trying to learn it all.
Below is my TaskModel and the only thing it does is get and set the TaskName.
namespace ToDoList.Model
{
public class TaskModel
{
private string taskName;
public string TaskName {
get { return taskName; }
set { taskName = value; }
}
}
}
I wrote the following code hoping that it would allow me to change the value of the TaskName but it does not seem to be working. Is there anyhting else that I should add to the code for it change the TaskName properly? Any tips or anything that could help me with this problem.
Below is the XAML code for my main window. It is a really simple UI, it features a ListBox which uses an ObservableCollection as its itemsource and when the listbox has a new item put into it has a checkbox to the left of it.
Below I will include the main window XAML and the C# code.
<Window x:Class="ToDoList.DemoMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ToDoList"
mc:Ignorable="d"
Title="The To-Do List" Height="500" Width="500" FontSize="22"
Background="White">
<Grid Margin="10" Background="BlueViolet">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2">
<TextBlock>Tasks to do:</TextBlock>
<!-- <TextBlock>Blah blah</TextBlock> -->
<!-- <local:UCLabelTextBxInput x:Name="TxtUCSaveToFileLocation" Title="Save to File Location" MaxLength="50"></local:UCLabelTextBxInput> -->
<ListBox x:Name="LstBoxTasks" MinHeight="200" MaxHeight="200" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Mode=OneWay}" Content="{Binding TaskName, Mode=TwoWay}" FontSize="14"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<local:UCLabelTextBxInput x:Name="TxtUCEnteredTask" Title="Enter Task Here:" MaxLength="50"></local:UCLabelTextBxInput>
<Button x:Name="BtnAddTask" Click="BtnAddTask_Click" Background="Chocolate">Add Task to List</Button>
</StackPanel>
<Button Grid.Column="0" Grid.Row="1" Margin="0,10,20,0" x:Name="btnDeleteTask" Click="BtnDeleteTask_Click" Background="Chocolate">Delete Task</Button>
<Button Grid.Column="1" Grid.Row="1" Margin="20,10,0,0" Background="Chocolate" Click="BtnEditTask_Click">Edit Task</Button>
<Button Grid.Column="0" Grid.Row="2" Margin="0,10,0,10" Grid.ColumnSpan="2" Background="Chocolate" Click="BtnHelp_Click">Help</Button>
</Grid>
</Window>
Then finally here is the C# code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using ToDoList.Model;
namespace ToDoList
{
/// <summary>
/// Interaction logic for DemoMainWindow.xaml
/// </summary>
public partial class DemoMainWindow : Window
{
SaveDataModel saveDataModel = new SaveDataModel();
ObservableCollection<TaskModel> tasksModels = new ObservableCollection<TaskModel>();
public DemoMainWindow()
{
InitializeComponent();
TxtUCEnteredTask.txtLimitedInput.Text = "Do the dishes";
LstBoxTasks.ItemsSource = tasksModels;
}
private void BtnAddTask_Click(object sender, RoutedEventArgs e)
{
tasksModels.Add(new TaskModel() { TaskName = TxtUCEnteredTask.txtLimitedInput.Text });
}
private void BtnDeleteTask_Click(object sender, RoutedEventArgs e)
{
if(LstBoxTasks.SelectedItem != null)
{
tasksModels.Remove(LstBoxTasks.SelectedItem as TaskModel);
}
}
private void BtnHelp_Click(object sender, RoutedEventArgs e)
{
HelpWindow helpWindow = new HelpWindow();
helpWindow.Show();
}
private void BtnEditTask_Click(object sender, RoutedEventArgs e)
{
if (LstBoxTasks.SelectedItem != null)
{
tasksModels[LstBoxTasks.SelectedIndex].TaskName = TxtUCEnteredTask.txtLimitedInput.Text;
}
}
}
}
Your property TaskName is not calling the OnPropertyChanged() method that notifies your view that the name changed.
You can do something like that:
private string taskName;
public string TaskName {
get { return taskName; }
set {
if( value != taskName) {
taskName = value;
OnPropertyChanged("TaskName");
}
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
Remarks:
The best way to do WPF is to do MVVM.
You should check out this link : https://www.codeproject.com/Tips/806587/Basic-MVVM-Listbox-Binding-in-WPF

WPF databinding of ICommand "freezes" after multiple Button clicks

This is my first time trying to databind an ICommand. I have a digital LED control that I would like to act like a Button, so I changed the DataTemplate for a Button control to look like an LED:
LED.xaml
<UserControl x:Class="LedControlDatabindingTest.LED"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="root"
mc:Ignorable="d"
Height="Auto" Width="Auto">
<Grid DataContext="{Binding ElementName=root}">
<StackPanel Orientation="{Binding LEDOrientation, FallbackValue=Vertical}">
<!-- LED portion -->
<Button BorderBrush="Transparent" Background="Transparent" Click="Button_Click">
<Button.ContentTemplate>
<DataTemplate>
<Grid>
<Ellipse Grid.Column="0" Margin="3" Height="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Width="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Fill="{Binding ElementName=root, Path=LEDColor, FallbackValue=Green}"
StrokeThickness="2" Stroke="DarkGray" HorizontalAlignment="Center" />
<Ellipse Grid.Column="0" Margin="3" Height="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}"
Width="{Binding ElementName=root, Path=LEDSize, FallbackValue=16}" HorizontalAlignment="Center">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,1.0">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.4" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</DataTemplate>
</Button.ContentTemplate>
</Button>
<!-- label -->
<TextBlock Grid.Column="1" Margin="3" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding LEDLabel, FallbackValue=0}" />
</StackPanel>
</Grid>
</UserControl>
I want the host application to be able to databind to properties like size, color, and label of the LED. In addition, I want to be able to bind to a command handler.
LED.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LedControlDatabindingTest {
/// <summary>
/// Interaction logic for LED.xaml
/// </summary>
public partial class LED : UserControl
{
public static DependencyProperty LEDColorProperty = DependencyProperty.Register( "LEDColor", typeof(Brush), typeof(LED));
public Brush LEDColor
{
get { return this.GetValue(LEDColorProperty) as Brush; }
set {
this.SetValue( LEDColorProperty, value);
}
}
public static DependencyProperty LEDSizeProperty = DependencyProperty.Register( "LEDSize", typeof(int), typeof(LED));
public int LEDSize
{
get { return (int)GetValue(LEDSizeProperty); }
set {
SetValue( LEDSizeProperty, value);
}
}
public static DependencyProperty LEDLabelProperty = DependencyProperty.Register( "LEDLabel", typeof(string), typeof(LED));
public string LEDLabel
{
get { return (string)GetValue(LEDLabelProperty); }
set {
SetValue( LEDLabelProperty, value);
}
}
public static DependencyProperty LEDOrientationProperty = DependencyProperty.Register( "LEDOrientation", typeof(Orientation), typeof(LED));
public Orientation LEDOrientation
{
get { return (Orientation)GetValue(LEDOrientationProperty); }
set {
SetValue( LEDOrientationProperty, value);
}
}
public static readonly DependencyProperty LEDClickedProperty = DependencyProperty.Register("LEDClicked", typeof(ICommand), typeof(LED), new PropertyMetadata(null));
public ICommand LEDClicked
{
get { return (ICommand)GetValue(LEDClickedProperty); }
set { SetValue( LEDClickedProperty, value); }
}
public LED()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LEDClicked.Execute( null);
}
}
}
My test application is simple.
MainWindow.xaml:
<Window x:Class="LedControlDatabindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LedControlDatabindingTest"
Title="MainWindow" Height="70" Width="250">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Digital Inputs:" />
<ListBox ItemsSource="{Binding AvailableDigitalInputs}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<local:LED LEDLabel="{Binding Index}" LEDColor="{Binding Color}" LEDSize="12" LEDClicked="{Binding Clicked}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Window>
There are so no-nos in my code-behind, like the DataContext for my application, but I think for the purposes of this demo it's okay for now.
MainWindow.xaml.cs:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LedControlDatabindingTest {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private class DigitalInputData : ViewModelBase
{
private Brush _color;
public Brush Color
{
get { return _color; }
set {
_color = value;
RaisePropertyChanged();
}
}
public int Index { get; set; }
public ICommand Clicked { get; set; }
private bool _state;
public DigitalInputData( int index, Brush on_color)
{
Index = index;
Color = Brushes.LightGray;
Clicked = new RelayCommand( () => {
// get current state of this digital input and then toggle it
_state = !_state;
// read back and update here until I get threaded updates implemented
Color = _state ? on_color : Brushes.LightGray;
});
}
}
private List<DigitalInputData> _inputs = new List<DigitalInputData>();
public ICollectionView AvailableDigitalInputs { get; set; }
public MainWindow()
{
InitializeComponent();
// For this example only, set DataContext in this way
DataContext = this;
for( int i=0; i<4; i++) {
_inputs.Add( new DigitalInputData( i, Brushes.Green));
}
AvailableDigitalInputs = CollectionViewSource.GetDefaultView( _inputs);
}
}
}
When I run this application, everything renders properly and according to my databound properties. The click handler works as well, and toggles the state of the LED.
But when I click the LED button numerous times, at some point (maybe after 20 clicks or so), it stops calling my databound ICommand. Why?
I got lucky and figured out a solution to the "freezing" problem, although I do not understand the technical reasoning.
In my DigitalInputData constructor, I created the handler for the RelayCommand using a lambda function instead of passing a reference to a handler. Once I switched over to passing the handler to the RelayCommand constructor, it worked great.

Silverlight controls defined in xaml are null at runtime

I have made a user control called Section which has 2 custom DependencyProperty: Title and Elements (kinda like content). I have used Section on several pages it displays just fine on every page.
The following xaml code is a part of my AccountView class
<Controls:Section Title="Epic Movies" Grid.Column="1" Grid.Row="1" x:Name="DescriptionSection" Visibility="Collapsed">
<Controls:Section.Elements>
<StackPanel>
<TextBlock Style="{StaticResource FrameLabel}" Text="Description:" Grid.Column="1"/>
<TextBlock x:Name="DescriptionField" Style="{StaticResource FrameText}" Text="some text..." Grid.Column="1" Grid.Row="1"/>
<TextBlock Text="Products:" Style="{StaticResource FrameLabel}"/>
<ScrollViewer Margin="12,0" VerticalScrollBarVisibility="Auto" MaxHeight="180">
<StackPanel x:Name="ProductList"/>
</ScrollViewer>
<TextBlock Style="{StaticResource FrameLabel}" Text="Category"/>
<TextBlock x:Name="CategoryField" Style="{StaticResource FrameText}" Text="some category"/>
<TextBlock Style="{StaticResource FrameLabel}" Text="Views:"/>
<TextBlock x:Name="ViewsField" Style="{StaticResource FrameText}" Text="12322 Views"/>
<TextBlock Style="{StaticResource FrameLabel}" Text="Price:"/>
<TextBlock x:Name="PriceField" Style="{StaticResource FrameText}" Text="455$"/>
<Button Content="Go to Module" Grid.Column="1" FontSize="15" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="20,15"/>
</StackPanel>
</Controls:Section.Elements>
</Controls:Section>
When i try set the text of my TextBlocks in c# code all the TextBlock are null
private void ShowModuleInformation(Module m)
{
DescriptionSection.Title = m.Name;
//DescriptionField is null even though clearly defined in xaml
DescriptionField.Text = m.Description;
PriceField.Text = m.Price + "";
ViewsField.Text = m.views+"";
CategoryField.Text = m.Category.Name;
foreach (Product p in m.Products)
{
TextBlock t = new TextBlock() { Text = p.Name };
ProductList.Children.Add(t);
}
DescriptionSection.Visibility = Visibility.Visible;
}
My Section c# class looks as follow:
public partial class Section : UserControl
{
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
"Title", typeof(string), typeof(Section), new PropertyMetadata(new PropertyChangedCallback(TitleChanged)));
public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register(
"Elements", typeof(UIElement), typeof(Section), new PropertyMetadata(new PropertyChangedCallback(ElementsChanged)));
public Section()
{
InitializeComponent();
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public UIElement Elements
{
get { return (UIElement)GetValue(ElementsProperty); }
set { SetValue(ElementsProperty, value); }
}
private static void TitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sec = (Section)d;
if (sec != null)
{
sec.TitleField.Text = (string)e.NewValue;
}
}
private static void ElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Section sec = (Section)d;
if (sec != null)
{
sec.ElementPanel.Content = (UIElement)e.NewValue;
}
}
I have solved this with a little help from this thread. All i needed to do was to make Section derive ContentControl instead of User Control

Is there some way I can use assign a value in MainPage and bind it in the child window

I've a Silverlight application wherein I've a MainPage in which I need to assign a variable Name in the childwindow and assign it without using the object of the child. I need to bind thise value to a textbox in the Childwindow through XAML. How can it be done?
So far what I've done is using a dependancy property in the childwindow:
nameProp = DependencyProperty.Register("strName", typeof(string), typeof(TestWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnNameChange)));
static TestWindow()
{
nameProp = DependencyProperty.Register("strName", typeof(string), typeof(TestWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnNameChange)));
}
private static void OnNameChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(nameProp, e.NewValue);
}
public string strName
{
get {
return (string)GetValue(nameProp);
}
set {
SetValue(nameProp, value);
}
}
and in TestWindow XAML i try to bind it:
<TextBox Text="{Binding Path=strName}" Height="23" HorizontalAlignment="Left" Margin="126,84,0,0" Name="txtName" VerticalAlignment="Top" Width="120"/>
How can I set the value for this dp from MainPage. Or is there any better alternative?
One way will be:
Make those variables public properties of your MainPage.
Assign the mainPage to be your child window DataContext.
Bind to those properties via XAML in your childWindow.
i am hoping that this will helps you....what you are trying to achieve....
ChildWindow Xaml file:
<controls:ChildWindow x:Class="ParentToChildWindow.ChildWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="400" Height="300"
Title="Pass Data from Parent to ChildWindow">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<TextBlock x:Name="txtValue" />
<TextBlock x:Name="txtName"/>
</StackPanel>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23"
HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</controls:ChildWindow>
ChildWindow CS file:
namespace ParentToChildWindow
{
using System.Windows;
using System.Windows.Controls;
public partial class ChildWindowControl : ChildWindow
{
public int Value { get; set; }
public string Name { get; set; }
public ChildWindowControl()
{
InitializeComponent();
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.txtValue.Text = this.Value.ToString();
this.txtName.Text = this.Name;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}
}
Parent CS File: I have added a button to parent XAML and added a click event
private void HandleButtonClickEvent(object sender, RoutedEventArgs e)
{
ChildWindowControl childControl = new ChildWindowControl();
childControl.Value = 10;
childControl.Name = "Data From Parent XAML to ChildWindow";
childControl.Show();
}
Pass the value in the constructor of theChildWindow
For this the place where we create the new instance of ChildWindow, we need to pass the required values to the Constructor. But remember, there must be a matching constructor already present in the ChildWindow control.
public ChildWindowControl(int value, string name)
{
InitializeComponent();
this.Value = value;
this.Name = name;
}
That is all that is required to pass the data from Parent XAML to ChildWindow

Dynamic View Animations using MVVM

I've been trying to figure out how to effectively trigger animations in a View when a property in the ViewModel updates, where the animation depends on the value of said property.
I've recreated my problem in a simple application with a single View and ViewModel. The goal here is to transition the color change of a rectangle by using a ColorAnimation. For reference, I've been using the MVVM Foundation package by Josh Smith.
The example project can be downloaded here.
To summarize, I want to animate the color transition in the View whenever the Color property changes.
MainWindow.xaml
<Window x:Class="MVVM.ColorAnimation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ColorAnimation:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle Margin="10">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}"/>
</Rectangle.Fill>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100">Blue</Button>
<Button Command="{Binding GreenCommand}" Width="100">Green</Button>
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
namespace MVVM.ColorAnimation
{
using System.Windows.Input;
using System.Windows.Media;
using MVVM;
public class MainWindowViewModel : ObservableObject
{
private ICommand blueCommand;
private ICommand greenCommand;
public ICommand BlueCommand
{
get
{
return this.blueCommand ?? (this.blueCommand = new RelayCommand(this.TurnBlue));
}
}
private void TurnBlue()
{
this.Color = Colors.Blue;
}
public ICommand GreenCommand
{
get
{
return this.greenCommand ?? (this.greenCommand = new RelayCommand(this.TurnGreen));
}
}
private void TurnGreen()
{
this.Color = Colors.Green;
}
private Color color = Colors.Red;
public Color Color
{
get
{
return this.color;
}
set
{
this.color = value;
RaisePropertyChanged("Color");
}
}
}
}
Is there anyway from the View to trigger a ColorAnimation instead of an instant transition between the values? The way I'm currently doing this is another application is quite messy, in that I set the ViewModel through a ViewModel property, and then using a PropertyObserver to monitor value changes, then create the Animation and trigger it from the codebehind:
this.colorObserver = new PropertyObserver<Player>(value)
.RegisterHandler(n => n.Color, this.CreateColorAnimation);
In a situation where I'm dealing with many colors and many potential animations, this becomes quite a mess, and messes up the fact that I'm manually passing in the ViewModel to the View than simply binding the two through a ResourceDictionary. I suppose I could do this in the DataContextChanged event as well, but is there a better way?
If just for a few animations I would recommend using Visual States. Then you can use GoToAction behavior on the view to trigger different animations. If you are dealing with a lot of similar animations, creating your own behavior would be a better solution.
Update
I have created a very simple behaivor to give a Rectangle a little color animation. Here is the code.
public class ColorAnimationBehavior : TriggerAction<FrameworkElement>
{
#region Fill color
[Description("The background color of the rectangle")]
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
#endregion
protected override void Invoke(object parameter)
{
var rect = (Rectangle)AssociatedObject;
var sb = new Storyboard();
sb.Children.Add(CreateVisibilityAnimation(rect, new Duration(new TimeSpan(0, 0, 1)), FillColor));
sb.Begin();
}
private static ColorAnimationUsingKeyFrames CreateVisibilityAnimation(DependencyObject element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame { KeyTime = new TimeSpan(duration.TimeSpan.Ticks), Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
In xaml, you simply attach this behavior like this,
<Rectangle x:Name="rectangle" Fill="Black" Margin="203,103,217,227" Stroke="Black">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:ColorAnimationBehavior FillColor="Red"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
When you click the Rectangle, it should go from Black color to Red.
I used the code that Xin posted, and made a few very minor tweeks (code is below). The only 3 material differences:
I created the behavior to work on any UIElement, not just a rectangle
I used a PropertyChangedTrigger instead of an EventTrigger. That let's me Monitor the color property on the ViewModel instead of listening for click events.
I bound the FillColor to the Color property of the ViewModel.
To use this, you will need to download the Blend 4 SDK (it's free, and you only need it if you don't already have Expression Blend), and add references to System.Windows.Interactivity, and Microsoft.Expression.Interactions
Here's the code for the behavior class:
// complete code for the animation behavior
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace ColorAnimationBehavior
{
public class ColorAnimationBehavior: TriggerAction<UIElement>
{
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
public Duration Duration
{
get { return (Duration)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
// Using a DependencyProperty as the backing store for Duration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(Duration), typeof(ColorAnimationBehavior), null);
protected override void Invoke(object parameter)
{
var storyboard = new Storyboard();
storyboard.Children.Add(CreateColorAnimation(this.AssociatedObject, this.Duration, this.FillColor));
storyboard.Begin();
}
private static ColorAnimationUsingKeyFrames CreateColorAnimation(UIElement element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame() { KeyTime = duration.TimeSpan, Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
}
Now here's the XAML that hooks it up to your rectangle:
<UserControl x:Class="MVVM.ColorAnimation.Silverlight.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ca="clr-namespace:ColorAnimationBehavior;assembly=ColorAnimationBehavior"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<ColorAnimation:MainWindowViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle x:Name="rectangle" Margin="10" Stroke="Black" Fill="Red">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding Color}">
<ca:ColorAnimationBehavior FillColor="{Binding Color}" Duration="0:0:0.5" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100" Content="Blue"/>
<Button Command="{Binding GreenCommand}" Width="100" Content="Green"/>
</StackPanel>
</Grid>
</UserControl>
It was really Xin's idea -- I just cleaned it up a bit.

Categories