I am working on an MVVM application and I would like to have a ProgressBar that smoothly animates to it's new value when that property changes. I have seen several answers to this question using c# but I'd prefer to do it all inside the template. The problem I'm having is setting up and targeting the event and storyboard properly. Here is what I have currently:
The progress bar-
The style- (just the triggers)
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="RangeBase.ValueChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="???????"
Storyboard.TargetProperty="Value"
To="???????" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
I took the trigger code from here: http://msdn.microsoft.com/en-us/library/system.windows.controls.progressbar(v=vs.110).aspx.
How do I set the TargetName to the template itself so that it applies to all the controls which use this template? How do I set "To" to the incoming Value? There appears to be a way to grab the "Binding" value but I have Value and Max both bound on the progressbar element. How would it know what to use?
Here is the whole template for reference:
<Style x:Key="ProgressStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid MinHeight="14" MinWidth="20">
<Border x:Name="BaseRectangle" Background="{StaticResource BaseColor}" CornerRadius="10,0,10,0"></Border>
<Border x:Name="GlassRectangle" CornerRadius="10,0,10,0" Background="{StaticResource GlassFX}" Panel.ZIndex="10"></Border>
<Border x:Name="animation" CornerRadius="10,0,10,0" Opacity=".7" Background="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left"></Border>
<Border x:Name="PART_Indicator" CornerRadius="10,0,10,0" Background="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left"></Border>
<Border x:Name="PART_Track" BorderThickness="1" CornerRadius="10,0,10,0" BorderBrush="Black"></Border>
<Border x:Name="BordeCabeceraSombra" BorderThickness="2" CornerRadius="10,0,10,0" BorderBrush="DarkGray" Opacity=".2" Margin="1,1,1,0"></Border>
<Label x:Name="Progress" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" Foreground="White" Opacity=".7" Content="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}"></Label>
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="RangeBase.ValueChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="???????"
Storyboard.TargetProperty="Value"
From="???????" To="???????" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsIndeterminate" Value="True">
<Setter Property="Visibility" TargetName="Progress" Value="Hidden"></Setter>
<Setter Property="Background" TargetName="PART_Indicator">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<wintheme:ProgressBarHighlightConverter/>
</MultiBinding.Converter>
<Binding Source="{StaticResource GlowFXProgressAnimated}"/>
<Binding Path="ActualWidth" ElementName="BaseRectangle"/>
<Binding Path="ActualHeight" ElementName="BaseRectangle"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".5"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Any help would be appreciated!
I think its better way.
You can create behavior to do this. (MVVM WPF)
Create class:
class ProgresBarAnimateBehavior : Behavior<ProgressBar>
{
bool _IsAnimating = false;
protected override void OnAttached()
{
base.OnAttached();
ProgressBar progressBar = this.AssociatedObject;
progressBar.ValueChanged += ProgressBar_ValueChanged;
}
private void ProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_IsAnimating)
return;
_IsAnimating = true;
DoubleAnimation doubleAnimation = new DoubleAnimation
(e.OldValue, e.NewValue, new Duration(TimeSpan.FromSeconds(0.3)), FillBehavior.Stop);
doubleAnimation.Completed += Db_Completed;
((ProgressBar)sender).BeginAnimation(ProgressBar.ValueProperty, doubleAnimation);
e.Handled = true;
}
private void Db_Completed(object sender, EventArgs e)
{
_IsAnimating = false;
}
protected override void OnDetaching()
{
base.OnDetaching();
ProgressBar progressBar = this.AssociatedObject;
progressBar.ValueChanged -= ProgressBar_ValueChanged;
}
}
And simply usage:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:YOURNAMESPACE.Behaviors"
<ProgressBar Height="7"
Value="{Binding LoadingValue}">
<i:Interaction.Behaviors>
<b:ProgresBarAnimateBehavior />
</i:Interaction.Behaviors>
</ProgressBar>
I never actually found a solution to this. I ended up just writing my own control. This isn't technically an answer to the question, but I figure I may as well post it. If someone is looking for an animating progress control for MVVM this may help.
namespace Card_System.Controls
{
/// <summary>
/// Interaction logic for StatProgressBar.xaml
/// </summary>
public partial class StatProgressBar : UserControl
{
private double _trackWidth;
private bool _isAnimate;
private bool _isRefresh;
public StatProgressBar()
{
InitializeComponent();
var descriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border));
if (descriptor != null)
{
descriptor.AddValueChanged(TrackBorder, ActualWidth_ValueChanged);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private double _barValueSet;
public double BarValueSet
{
get { return _barValueSet; }
set
{
_barValueSet = value;
OnPropertyChanged("BarValueSet");
_isAnimate = true;
AnimateWidth();
}
}
public double BarValueDesired
{
get { return (double)GetValue(BarValueProperty); }
set { SetValue(BarValueProperty, value); }
}
public static readonly DependencyProperty BarValueProperty =
DependencyProperty.Register("BarValueDesired", typeof(double), typeof(StatProgressBar), new UIPropertyMetadata(0.0d, new PropertyChangedCallback(BarValueDesired_PropertyChanged)));
public double BarMaximum
{
get { return (double)GetValue(BarMaximumProperty); }
set { SetValue(BarMaximumProperty, value); }
}
public static readonly DependencyProperty BarMaximumProperty =
DependencyProperty.Register("BarMaximum", typeof(double), typeof(StatProgressBar), new UIPropertyMetadata(0.0d));
public static void BarValueDesired_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Set BarValue to the value of BarValueDesired BEFORE it was just changed.
((StatProgressBar)d).BarValueSet = (double)e.OldValue;
}
public Brush BarColor
{
get { return (Brush)GetValue(BarColorProperty); }
set { SetValue(BarColorProperty, value); }
}
public static readonly DependencyProperty BarColorProperty =
DependencyProperty.Register("BarColor", typeof(Brush), typeof(StatProgressBar), new UIPropertyMetadata(Brushes.White, new PropertyChangedCallback(BarColor_PropertyChanged)));
public static void BarColor_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((StatProgressBar)d).BarFill.Background = (Brush)e.NewValue;
}
private void ActualWidth_ValueChanged(object a_sender, EventArgs a_e)
{
_trackWidth = TrackBorder.ActualWidth;
_isRefresh = true;
AnimateWidth();
}
public void AnimateWidth()
{
if (_isAnimate && _isRefresh)
{
double StartPoint = new double();
double EndPoint = new double();
double PercentEnd = new double();
double PercentStart = new double();
PercentStart = BarValueSet / BarMaximum;
StartPoint = _trackWidth * PercentStart;
PercentEnd = BarValueDesired / BarMaximum;
EndPoint = _trackWidth * PercentEnd;
DoubleAnimation animation = new DoubleAnimation(StartPoint, EndPoint, TimeSpan.FromSeconds(3));
this.BarFill.BeginAnimation(Border.WidthProperty, animation);
}
else return;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
And here is the XAML:
<Grid>
<Grid MinHeight="14" MinWidth="20">
<Border x:Name="BaseRectangle" Background="{StaticResource BaseColor}" CornerRadius="0,0,0,0"/>
<Border x:Name="TrackBorder" BorderThickness="1" CornerRadius="0,0,0,0" BorderBrush="Black" Panel.ZIndex="20"/>
<Border x:Name="BarFill" HorizontalAlignment="Left" Opacity=".7" Background="White"/>
<Border x:Name="GlassOverlay" CornerRadius="0,0,0,0" Background="{StaticResource GlassFX}" Panel.ZIndex="10"/>
<Border x:Name="GlassOverlayBorder" BorderThickness="4" CornerRadius="0,0,0,0" BorderBrush="DarkGray" Opacity=".2" Panel.ZIndex="12"/>
</Grid>
I know this question is solved, but I found a really good implementation that doesn't require creating a UserControl. It imitates the "barber pole effect" and works right out of the box:
<SolidColorBrush x:Key="ProgressBarBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarBackgroundBrush" Color="White" />
<SolidColorBrush x:Key="ProgressBarTrackBackgroundBrush" Color="#63D055" />
<Style x:Key="{x:Type ProgressBar}" TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<local:ClippingBorder x:Name="BorderBackground" CornerRadius="3" BorderThickness="0"
BorderBrush="{StaticResource ProgressBarBorderBrush}"
Background="{StaticResource ProgressBarBackgroundBrush}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Determinate" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="PART_Track" Margin="0" BorderThickness="0" CornerRadius="3" />
<Border x:Name="PART_Indicator" Margin="0" BorderThickness="0" CornerRadius="3" HorizontalAlignment="Left"
Background="{StaticResource ProgressBarTrackBackgroundBrush}" ClipToBounds="True">
<Border x:Name="DiagonalDecorator" Width="5000">
<Border.Background>
<DrawingBrush TileMode="Tile" Stretch="None" Viewbox="0,0,1,1" Viewport="0,0,36,34" ViewportUnits="Absolute">
<DrawingBrush.RelativeTransform>
<TranslateTransform X="0" Y="0" />
</DrawingBrush.RelativeTransform>
<DrawingBrush.Drawing>
<GeometryDrawing Brush="#48C739" Geometry="M0,0 18,0 36,34 18,34 Z" />
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.Background>
<Border.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Border.Background).(DrawingBrush.RelativeTransform).(TranslateTransform.X)"
From="0" To=".36" RepeatBehavior="Forever" Duration="0:0:18" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</Border>
</Grid>
</local:ClippingBorder>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Simply edit speed and colors to your liking.
Related
As topic mentioned.I want to use only one popup for all button in my application.I don't know how to get what I want.
Here is what my window looks like:
Info 1:
Info 2:
You can see popup appear on wrong position.I know I can position a popup by setting the PlacementTarget.But each Popup has a different value for the placement property.That is the problem.I'm looking another way to do it.
Here is a popup for option 1:
<StackPanel Orientation="Horizontal">
<!--Option 1: text and button-->
<TextBlock Text="Option 1"
Margin="10"
VerticalAlignment="Center" />
<Popup x:Name="popInfo"
PlacementTarget="{Binding ElementName=btnInfoOption1}"
IsOpen="{Binding IsShowInfo1}">
<ContentControl Style="{StaticResource ContentInfoStyle}">
<TextBlock Text="{Binding InfoContent}"
TextWrapping="Wrap"
Foreground="White"
Width="340"
Padding="10"
Margin="30,0,30,5"
FontSize="15" />
</ContentControl>
</Popup>
<Button x:Name="btnInfoOption1"
Style="{StaticResource btnIcons}"
Background="#0063b1"
Width="30"
Height="30"
Margin="10,10,20,10"
Command="{Binding CmdShowInfo, Delay=1500}"
Tag="{StaticResource ic_ginfo}" />
</StackPanel>
popup for option 2:
<StackPanel Orientation="Horizontal">
<!--Option 2: text and button-->
<TextBlock Text="Option 2"
Margin="10"
VerticalAlignment="Center" />
<Button x:Name="btnOption2"
Style="{StaticResource btnIcons}"
Background="#0063b1"
Width="30"
Height="30"
Margin="10,10,20,10"
Command="{Binding CmdShowInfo, Delay=1500}"
Tag="{StaticResource ic_ginfo}" />
</StackPanel>
ContentControl Style:
<Style TargetType="{x:Type ContentControl}"
x:Key="ContentInfoStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="Green"
CornerRadius="3"
Padding="10,0,12,10">
<StackPanel>
<Button HorizontalAlignment="Right"
Tag="{StaticResource ic_gclear}"
Style="{StaticResource btnIcons}"
Background="White"
Margin="10,5,12,5"
Command="{Binding DataContext.CmdCloseInfo}"
Height="24" />
<ContentPresenter x:Name="content"
TextBlock.FontSize="14"
TextBlock.Foreground="White"
TextBlock.FontFamily="Arial"
Content="{TemplateBinding ContentControl.Content}" />
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Button icon style:
<Style TargetType="Button"
x:Key="btnIcons">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="brd" Background="Transparent"
SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path Stretch="Uniform" VerticalAlignment="Center"
Fill="{TemplateBinding Background}"
Data="{TemplateBinding Tag}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ViewModel.cs:
the content of popup:
private string _InfoContent;
public string InfoContent
{
get { return _InfoContent; }
set
{
if (value != _InfoContent)
{
_InfoContent = value;
OnRaise("InfoContent");
}
}
}
show the popup for option2 and option1:
private bool _IsShowInfo2;
public bool IsShowInfo2
{
get { return _IsShowInfo2; }
set
{
if (value != _IsShowInfo2)
{
_IsShowInfo2 = value;
OnRaise("IsShowInfo2");
}
}
}
//show the popup for option1
private bool _IsShowInfo1;
public bool IsShowInfo1
{
get { return _IsShowInfo1; }
set
{
if (value != _IsShowInfo1)
{
_IsShowInfo1 = value;
OnRaise("IsShowInfo1");
}
}
}
the command for button:
private ICommand _CmdShowInfo;
public ICommand CmdShowInfo
{
get
{
_CmdShowInfo = _CmdShowInfo ?? new RelayCommand(x => this.ShowInfo(true, 1), () => true);
return _CmdShowInfo;
}
}
private ICommand _CmdShowInfo2;
public ICommand CmdShowInfo2
{
get
{
_CmdShowInfo2 = _CmdShowInfo2 ?? new RelayCommand(x => this.ShowInfo(true, 0), () => true);
return _CmdShowInfo2;
}
}
private void ShowInfo(bool show = true, byte option = 0)
{
if (option == 0)
{
this.InfoContent = "Option 1...";
}
else if (option == 1)
{
this.InfoContent = "Option 2...";
}
this.IsShowInfo1 = show;
}
My initial thought was to do this with a styled HeaderedContentControl, but then you've got the icon fill color and the icon data, and I'd have had to add attached properties for those. Once you go there, you may as well just write a custom control.
The dependency properties of IconPopupButton can be bound like any dependency property:
<hec:IconPopupButton
IsOpen="{Binding IsShowInfo1}"
IconFill="YellowGreen"
Content="Another Test Popup"
IconData="M -10,-10 M 0,3 L 17,20 L 20,17 L 3,0 Z M 0,0 L 0,20 L 20,20 L 20,0 Z"
/>
If you want to parameterize the Style applied to the ContentControl in the Popup, add another dependency property. You'll need to give that some thought, though, because you need that ToggleButton to be bound to IsOpen on the templated parent, one way or another. Perhaps you could bind it to the viewmodel property that's bound to the popup button's IsOpen. There's always a way.
So here's that. With snippets to create dependency properties, this is pretty much just a fill-in-the-blanks exercise. Much less to it than meets the eye.
IconPopupButton.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace HollowEarth.Controls
{
public class IconPopupButton : ContentControl
{
static IconPopupButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconPopupButton), new FrameworkPropertyMetadata(typeof(IconPopupButton)));
}
#region IconData Property
public Geometry IconData
{
get { return (Geometry)GetValue(IconDataProperty); }
set { SetValue(IconDataProperty, value); }
}
public static readonly DependencyProperty IconDataProperty =
DependencyProperty.Register("IconData", typeof(Geometry), typeof(IconPopupButton),
new PropertyMetadata(null));
#endregion IconData Property
#region IconFill Property
public Brush IconFill
{
get { return (Brush)GetValue(IconFillProperty); }
set { SetValue(IconFillProperty, value); }
}
public static readonly DependencyProperty IconFillProperty =
DependencyProperty.Register("IconFill", typeof(Brush), typeof(IconPopupButton),
new PropertyMetadata(SystemColors.ControlTextBrush));
#endregion IconFill Property
#region IsOpen Property
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(IconPopupButton),
new PropertyMetadata(false));
#endregion IsOpen Property
#region StaysOpen Property
public bool StaysOpen
{
get { return (bool)GetValue(StaysOpenProperty); }
set { SetValue(StaysOpenProperty, value); }
}
public static readonly DependencyProperty StaysOpenProperty =
DependencyProperty.Register("StaysOpen", typeof(bool), typeof(IconPopupButton),
new PropertyMetadata(false));
#endregion StaysOpen Property
#region Placement Property
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(IconPopupButton),
new PropertyMetadata(PlacementMode.Right));
#endregion Placement Property
}
}
Themes\Shared.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HeaderedPopupTest.Themes"
>
<Geometry x:Key="ic_gclear">M56,4 52,0 28,24 4,0 0,4 24,28 0,52 4,56 28,32 52,56 56,52 32,28Z</Geometry>
<Geometry x:Key="ic_ginfo">M31,0C13.879,0,0,13.879,0,31s13.879,31,31,31s31-13.879,31-31S48.121,0,31,0z M34,46h-6V27.969h6V46z M34,21.969h-6V16h6V21.969z</Geometry>
<Style TargetType="ButtonBase" x:Key="btnIcons">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border x:Name="brd" Background="Transparent" SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Path
x:Name="Path"
Stretch="Uniform"
VerticalAlignment="Center"
Fill="{TemplateBinding Background}"
Data="{TemplateBinding Tag}"
/>
<TextBlock
x:Name="MissingIconData"
Visibility="Collapsed"
Text="?"
FontWeight="Bold"
FontSize="30"
ToolTip="IconData (Tag) not set"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="{x:Null}">
<Setter TargetName="MissingIconData" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Themes\Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HeaderedPopupTest.Themes"
xmlns:hec="clr-namespace:HollowEarth.Controls"
>
<ResourceDictionary.MergedDictionaries>
<!-- Change HeaderedPopupTest to the name of your own assembly -->
<ResourceDictionary Source="/HeaderedPopupTest;component/Themes/Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="hec:IconPopupButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="hec:IconPopupButton">
<Grid x:Name="Grid">
<ToggleButton
x:Name="OpenButton"
Style="{StaticResource btnIcons}"
Background="{TemplateBinding IconFill}"
Tag="{TemplateBinding IconData}"
IsChecked="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="{TemplateBinding ToolTip}"
/>
<Popup
x:Name="Popup"
StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=ToggleButton}"
Placement="{TemplateBinding Placement}"
>
<Border
Background="Green"
CornerRadius="3"
Padding="10,0,12,10">
<StackPanel>
<ToggleButton
HorizontalAlignment="Right"
Tag="{StaticResource ic_gclear}"
Style="{StaticResource btnIcons}"
Background="White"
Margin="10,5,12,5"
IsChecked="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Height="24"
/>
<ContentPresenter
x:Name="content"
TextBlock.FontSize="14"
TextBlock.Foreground="White"
TextBlock.FontFamily="Arial"
Content="{TemplateBinding Content}"
/>
</StackPanel>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<!--
I don't understand this: If I use the templated parent's IsOpen,
the effect is as if it were never true.
-->
<Condition SourceName="Popup" Property="IsOpen" Value="True" />
<Condition Property="StaysOpen" Value="False" />
</MultiTrigger.Conditions>
<!--
If StaysOpen is false and the button is enabled while the popup is open,
then clicking on it will cause the popup to flicker rather than close.
-->
<Setter TargetName="OpenButton" Property="IsEnabled" Value="False" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml example usage:
<Window
x:Class="HeaderedPopupTest.MainWindow"
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:HeaderedPopupTest"
xmlns:hec="clr-namespace:HollowEarth.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes\Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style
x:Key="InfoPopupButton"
TargetType="hec:IconPopupButton"
BasedOn="{StaticResource {x:Type hec:IconPopupButton}}"
>
<Setter Property="IconFill" Value="DeepSkyBlue" />
<Setter Property="IconData" Value="{StaticResource ic_ginfo}" />
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel
Orientation="Vertical"
HorizontalAlignment="Left"
>
<hec:IconPopupButton
Style="{StaticResource InfoPopupButton}"
Content="This is a test popup"
ToolTip="Test Popup Tooltip"
/>
<hec:IconPopupButton
IconFill="YellowGreen"
Content="Another Test Popup"
IconData="M -10,-10 M 0,3 L 17,20 L 20,17 L 3,0 Z M 0,0 L 0,20 L 20,20 L 20,0 Z"
/>
<hec:IconPopupButton
IconFill="DarkRed"
Content="Missing IconData behavior example"
/>
</StackPanel>
</Grid>
</Window>
You'll notice I changed your buttons to ToggleButton. This is for convenience in wiring them up to the IsOpen property: With a ToggleButton, I just bind IsChecked and I'm done. No need for commands. One side effect of that is that if StaysOpen is false, then when the user clicks on the open button for a Popup, the focus change closes the Popup, which unchecks the button, and then the button gets the mouse message. So the button opens the popup again. This is bizarre behavior from the user's perspective, so you add a trigger to disable the button when the popup is open and StaysOpen is false. When StaysOpen is true, focus change doesn't close the Popup, so you want the button to be enabled in that case.
I changed the btnIcons style to target ButtonBase, so it works identically with Button and ToggleButton.
I'm received the exception Type reference cannot find type named '{clr-namespace:Dashboard.View}DashBoardColors at runtime.
I have a static class with my colors:
namespace Dashboard.View
{
public static class DashBoardColors
{
public static readonly Color TargetColor = Color.FromRgb(200, 240, 255);
public static readonly SolidColorBrush Red = new SolidColorBrush(Color.FromRgb(255, 0, 0));
public static readonly SolidColorBrush Stale = new SolidColorBrush(Color.FromRgb(200, 200, 200));
public static readonly SolidColorBrush Target = new SolidColorBrush(TargetColor);
public static readonly SolidColorBrush Dragging = new SolidColorBrush(Color.FromRgb(200, 255, 200));
public static readonly SolidColorBrush Good = Dragging;
}
}
My resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:Dashboard.View">
<Style x:Key="AnimatedSwitch" TargetType="{x:Type ToggleButton}">
<Setter Property="Foreground" Value="Silver" />
<Setter Property="Background" Value="Silver" />
<Setter Property="BorderBrush" Value="Silver" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Stretch="Uniform" Width="40">
<Canvas Name="Layer_1" Width="20" Height="20">
<Ellipse Canvas.Left="0" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Ellipse Canvas.Left="15" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Border Canvas.Left="10" Width="15" Height="20" Name="rect416927" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0.5,0,0.5"/>
<Ellipse x:Name="ellipse" Canvas.Left="0" Width="20" Height="20" Fill="White" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.3">
<Ellipse.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Ellipse.RenderTransform>
<Ellipse.BitmapEffect>
<DropShadowBitmapEffect Softness="0.1" ShadowDepth="0.7" Direction="270" Color="#BBBBBB"/>
</Ellipse.BitmapEffect>
</Ellipse>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.15"
Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="{x:Static view:DashBoardColors.TargetColor}" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard FillBehavior="Stop">
<ColorAnimation Duration="0:0:0.3"
Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="White" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And my usage within the usercontrol:
Included the namespace:
xmlns:view="clr-namespace:Dashboard.View"
Merged the dictionary:
<UserControl.Resources>
<ResourceDictionary x:Key="Styles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Dashboard;component/View/Styles/AnimatedStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Applied the style:
<ToggleButton Style="{StaticResource AnimatedSwitch}" Height="20" x:Name="DateSelectToggle" />
The problem is in setting the following:
To="{x:Static view:DashBoardColors.TargetColor}"
Oops,
I had the build action on View/Styles/AnimatedStyles.xaml set to resource which means that namespace needed to include the assembly, if it is not current:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:Dashboard.View;assembly=Dashboard">
Or set the build action to Page and now it works.
I have created a custom water mark text box which is extended from text box. control template for the same is shown below.
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:WaterMarkTextBox}">
<ControlTemplate.Resources>
<Storyboard x:Key="Storyboard1">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="PART_FieldTextBlock">
<SplineThicknessKeyFrame KeyTime="0:0:0.15"
Value="0,0,10,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard2">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="PART_FieldTextBlock">
<SplineThicknessKeyFrame KeyTime="0:0:0.25"
Value="0,0,-500,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="PART_GridControl"
ClipToBounds="True"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}">
<TextBlock x:Name="PART_PlaceHolderTextBlock"
Style="{StaticResource SWMLightTextBlockStyle}"
Foreground="#BDBBBB"
FontSize="{StaticResource SmallFontSize}"
Text="{TemplateBinding PlaceHolderText}"
VerticalAlignment="Center"
Margin="20,0,10,0" />
<Border Name="border"
CornerRadius="0"
Padding="2"
BorderThickness="1"
BorderBrush="DeepSkyBlue">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<TextBlock x:Name="PART_FieldTextBlock"
HorizontalAlignment="Right"
Foreground="#BDBBBB"
Margin="0,0,-500,0"
Style="{StaticResource SWMLightTextBlockStyle}"
FontSize="{StaticResource SmallFontSize}"
TextWrapping="Wrap"
Text="{TemplateBinding FieldText}"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
but while typing the textbox enters to a condition where it is having no cursor but we can type into it occurs with a probability of 1/2 chars.I wonder how it happens. Anyone is having idea how it is happening?
Two things You can do. First One is Overriding SystemParameters Properties Using reflection Like this
void LocallyDisableMouseVanish()
{
foreach (var field in typeof(SystemParameters).GetFields(BindingFlags.NonPublic|BindingFlags.Static))
if (field.Name.Contains("mouseVanish"))
field.SetValue(null, false);
}
Call this method On Focus Of your Control.
If this is Not working means You can Try something Like this Override the style of the Caret Like this
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:WaterMarkTextBox}">
<ControlTemplate.Resources>
<Storyboard x:Key="Storyboard1">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="PART_FieldTextBlock">
<SplineThicknessKeyFrame KeyTime="0:0:0.15"
Value="0,0,10,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard2">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="PART_FieldTextBlock">
<SplineThicknessKeyFrame KeyTime="0:0:0.25"
Value="0,0,-500,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="PART_GridControl"
ClipToBounds="True"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}">
<TextBlock x:Name="PART_PlaceHolderTextBlock"
Style="{StaticResource SWMLightTextBlockStyle}"
Foreground="#BDBBBB"
FontSize="{StaticResource SmallFontSize}"
Text="{TemplateBinding PlaceHolderText}"
VerticalAlignment="Center"
Margin="20,0,10,0" />
<Border Name="border"
CornerRadius="0"
Padding="2"
BorderThickness="1"
BorderBrush="DeepSkyBlue">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
<TextBlock x:Name="PART_FieldTextBlock"
HorizontalAlignment="Right"
Foreground="#BDBBBB"
Margin="0,0,-500,0"
Style="{StaticResource SWMLightTextBlockStyle}"
FontSize="{StaticResource SmallFontSize}"
TextWrapping="Wrap"
Text="{TemplateBinding FieldText}"
VerticalAlignment="Center" />
<Canvas>
<Border x:Name="PART_Caret"
Visibility="Collapsed"
Canvas.Left="0"
Canvas.Top="0"
Width="5"
Height="25"
Background="Black"
BorderThickness="1">
<Border.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard x:Name="CaretStoryBoard"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.Color"
Duration="0:0:0:1"
FillBehavior="HoldEnd">
<ColorAnimationUsingKeyFrames.KeyFrames>
<DiscreteColorKeyFrame KeyTime="0:0:0.750"
Value="Transparent" />
<DiscreteColorKeyFrame KeyTime="0:0:0.000"
Value="Black" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="CaretBrush "
Value="Transparent" />
And in your control code Add this
public override void OnApplyTemplate()
{
this.border = this.GetTemplateChild("PART_Caret") as Border;
base.OnApplyTemplate();
}
And add this Method
private void MoveCustomCaret()
{
var caretLocation = this.GetRectFromCharacterIndex(this.CaretIndex).Location;
if (!double.IsInfinity(caretLocation.X))
{
Canvas.SetLeft(border, caretLocation.X);
}
if (!double.IsInfinity(caretLocation.Y))
{
Canvas.SetTop(border, caretLocation.Y);
}
}
And Finally Call This method and set the visibility of the border
private void SWMTextBox_GotFocus(object sender, RoutedEventArgs e)
{
MoveCustomCaret();
border.Visibility = Visibility.Visible;
}
also in TextChangedEvent
private void CustomTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
MoveCustomCaret();
}
And Hid the Visibility in Lost_Focus_Event
private void SWMTextBox_LostFocus(object sender, RoutedEventArgs e)
{
border.Visibility = Visibility.Hidden;
}
Hello I have a problem accessing User Control Properties. My User Control Looks like this:
<UserControl.Resources>
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnChecking">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="25"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnUnchecking">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(FrameworkElement.Margin)">
<SplineThicknessKeyFrame KeyTime="00:00:00.3000000" Value="1,1,1,1"/>
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border Name="Border">
<DockPanel x:Name="dockPanel">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" RecognizesAccessKey="True" VerticalAlignment="Center"/>
<Grid Width="50" x:Name="grid">
<TextBlock Text="ON" TextWrapping="Wrap" FontWeight="Bold" FontSize="12" HorizontalAlignment="Right" Margin="0,0,3,0"/>
<TextBlock HorizontalAlignment="Left" Margin="2,0,0,0" FontSize="12" FontWeight="Bold" Text="OFF" TextWrapping="Wrap"/>
<Border HorizontalAlignment="Left" x:Name="slider" Width="23" BorderThickness="1,1,1,1" CornerRadius="3,3,3,3" RenderTransformOrigin="0.5,0.5" Margin="1,1,1,1">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Border.RenderTransform>
<Border.BorderBrush>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#FF4490FF" Offset="1"/>
</LinearGradientBrush>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF8AB4FF" Offset="1"/>
<GradientStop Color="#FFD1E2FF" Offset="0"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource OnUnchecking}" x:Name="OnUnchecking_BeginStoryboard"/>
</Trigger.ExitActions>
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource OnChecking}" x:Name="OnChecking_BeginStoryboard"/>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<CheckBox x:Name="CheckBox1" HorizontalAlignment="Center"
Style="{DynamicResource CheckBoxStyle1}"
VerticalAlignment="Center"
Checked="CheckBox1_OnChecked"
Unchecked="CheckBox1_OnUnchecked"/>
</Grid>
And in the code behind(some of a great number of getters/setters):
public new Brush Background
{
get
{
var border = CheckBox1.Template.LoadContent() as Border;
return border == null ? Brushes.White : border.Background;
}
set
{
var border = CheckBox1.Template.LoadContent() as Border;
if (border == null) return;
border.Background = value;
}
}
public new Thickness BorderThickness
{
get
{
var border = CheckBox1.Template.LoadContent() as Border;
return border == null ? new Thickness(0) : border.BorderThickness;
}
set
{
var border = CheckBox1.Template.LoadContent() as Border;
if (border == null) return;
border.BorderThickness = value;
}
}
public new Brush BorderBrush
{
get
{
var border = CheckBox1.Template.LoadContent() as Border;
return border == null ? Brushes.Transparent : border.BorderBrush;
}
set
{
var border = CheckBox1.Template.LoadContent() as Border;
if (border == null) return;
border.BorderBrush = value;
}
}
And also i use it in another window like this(SliderOnOff is the name of my control):
<switch:SliderOnOff x:Name="SliderOnOff"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsEnabled="True"
Background="Green"
BorderThickness="10"
BorderBrush="HotPink"
IsChecked="True"
BorderRadius="5"
Checked="SliderOnOff_OnChecked"
UnChecked="SliderOnOff_OnUnChecked">
</switch:SliderOnOff>
I thought it works fine, because setting properties in xaml leaded to changing in properties of control.
BUT!!!!!!
When I try to change it programmatically ( Slider.Background = Brushes.HotPink; ), nothing happens, it doesnt change. Also this part of properties doesnt work at all(none in xaml, none in code):
public CornerRadius BorderRadius
{
get
{
var border = CheckBox1.Template.LoadContent() as Border;
return border == null ? new CornerRadius(0) : border.CornerRadius;
}
set
{
var border = CheckBox1.Template.LoadContent() as Border;
if (border == null) return;
border.CornerRadius = new CornerRadius(value.TopLeft,value.TopRight,value.BottomRight,value.BottomLeft);
}
}
Can you help me with it?
I think your ui hast to be informed about the changes.
Have a look at: http://www.codeproject.com/Articles/42536/List-vs-ObservableCollection-vs-INotifyPropertyCha
Your class must implement INotifyPropertyChanged, and within your "set" part you have to raise that event.
like:
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
public KeyInfo SelectedKeyInfo
{
get
{
return _SelectedKeyInfo;
}
set
{
_SelectedKeyInfo = value;
OnPropertyChanged("SelectedKeyProducts");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Scrolling is horizontal
Touch-driven.
Items flow down, then into the next column
Touching an item would scroll the panel to a set point so the detail view would always be in the same spot.
The next column over would "break off" and animate to the right, to reveal a details pane in the context of the selected item.
Touching any visible item (in a different column) would "close" the revealed detail, then animate the new selected item to the left static point and again cut the next column away to reveal the detail. Touching any visible item in the same column would just do a fade-out-in animation.
Here are some simple mocks:
90% of this is simple to me, but the process to create a wrap panel which can "separate" itself to reveal an item is eluding me in a big way. Any advice would be appreciated.
One of the solutions is :
You can separate buttons inside wrap panel(which is inside grid) by changing margin of some of them (of course you need to change size of window as well if you want to keep buttons size and avoid moving them to next line).
For example if you have 4 columns and three rows of buttons named button 1,2,3 etc...
when button from first column is clicked buttons 2,6,10 getting :
new thickness(space,0,0,0);
This moves all the buttons on the right by value of variable space;
And then
window.width += space;
And then textBox which is children of the grid is to locate in suitable place with width space.
On undo
new thickness(0,0,0,0);
window.width -= space;
It worked for me well but i am curious for other solutions.
This sounded like a fun task, so I decided to implement something along the lines of what you wanted. I thought I could share it so you can improve it or use it to your liking. First of, this task is beyond my skills of implementing in only XAML. I'm not saying it can't be done, just that it's a tad difficult. Instead, I implemented my own Panel type (called GapPanel). This might sound even worse, but there's a few nice things about it nonetheless, like the possibility of implementing RoutedEvents to respond to in XAML for animations.
So, here's the load of code. First the XAML
<Window x:Class="SlidingWrapPanel.SecondAttempt"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SlidingWrapPanel"
Title="Wrapped items with details pane" Height="250" Width="600">
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ItemButtonTemplate">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#999999" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#3e3e3e" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
<Border x:Name="ButtonBorder" Background="#3e3e3e" BorderBrush="#222" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}" Margin="0" />
</Border>
</ControlTemplate>
<Style x:Key="ItemGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Margin" Value="1"/>
</Style>
<Style x:Key="ItemButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<Binding Source="{StaticResource ItemButtonTemplate}"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="160"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="-160" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Segoe WP Light"/>
<Setter Property="Margin" Value="15"/>
</Style>
<Storyboard x:Key="ExpandColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
From="0" To="{Binding ActualWidth, ElementName=DetailsPanel}" Duration="0:0:0.75">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0" Value="{Binding GapX, ElementName=ItemsPanel}"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CollapseColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
To="0" Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="-160"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid>
<Grid>
<Grid x:Name="DetailsPanel" Style="{StaticResource DetailsGridStyle}">
<ScrollViewer>
<TextBlock Style="{StaticResource DetailsTextStyle}">
<Run Text="Details" FontSize="18"/>
<LineBreak />
<Run Text="Some text"/>
</TextBlock>
</ScrollViewer>
</Grid>
</Grid>
<local:GapPanel x:Name="ItemsPanel">
<local:GapPanel.Triggers>
<EventTrigger RoutedEvent="local:GapPanel.ColumnChanged">
<BeginStoryboard Storyboard="{StaticResource ExpandColumnAnimation}"/>
</EventTrigger>
<EventTrigger RoutedEvent="local:GapPanel.CloseGap">
<BeginStoryboard Storyboard="{StaticResource CollapseColumnAnimation}"/>
</EventTrigger>
</local:GapPanel.Triggers>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 1" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 2" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 3" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 4"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 5"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 6"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 7"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 8"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 9"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 10"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 11"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 12"/>
</Grid>
</local:GapPanel>
</Grid>
</Window>
And the (abomination) of a panel..
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SlidingWrapPanel {
public class GapPanel : Panel, INotifyPropertyChanged {
private readonly IDictionary<UIElement, int> columns;
private readonly IDictionary<int, double> gapCoordinates;
private object opened;
public static readonly DependencyProperty GapColumnProperty =
DependencyProperty.Register("GapColumn", typeof(int), typeof(GapPanel), new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender, columnChanged));
public static readonly DependencyProperty GapWidthProperty =
DependencyProperty.Register("GapWidth", typeof(double), typeof(GapPanel), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly RoutedEvent ColumnChangedEvent;
public static readonly RoutedEvent CloseGapEvent;
static GapPanel() {
ColumnChangedEvent = EventManager.RegisterRoutedEvent("ColumnChanged", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
CloseGapEvent = EventManager.RegisterRoutedEvent("CloseGap", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
}
public GapPanel() {
columns = new Dictionary<UIElement, int>();
gapCoordinates = new Dictionary<int, double>();
GapWidth = 0;
GapColumn = -1;
}
public int GapColumn {
get { return (int)GetValue(GapColumnProperty); }
set { SetValue(GapColumnProperty, value); }
}
public double GapWidth {
get { return (double)GetValue(GapWidthProperty); }
set { SetValue(GapWidthProperty, value); }
}
public double GapX {
get {
double value;
gapCoordinates.TryGetValue(GapColumn, out value);
return value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event RoutedEventHandler ColumnChanged {
add { AddHandler(ColumnChangedEvent, value); }
remove { RemoveHandler(ColumnChangedEvent, value); }
}
public event RoutedEventHandler CloseGap {
add { AddHandler(CloseGapEvent, value); }
remove { RemoveHandler(CloseGapEvent, value); }
}
protected override Size ArrangeOverride(Size finalSize) {
Point location = new Point();
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > finalSize.Height && columnWidth > 0) {
location.X += columnWidth;
if (col == GapColumn) {
location.X += GapWidth;
}
++col;
columnWidth = 0;
position = child.DesiredSize.Height;
location.Y = 0;
}
columns[child] = col;
child.Arrange(new Rect(location, child.DesiredSize));
location.Y = position;
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
double width = 0, height = 0;
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
child.Measure(availableSize);
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > availableSize.Height && columnWidth > 0) {
width += columnWidth;
++col;
columnWidth = child.DesiredSize.Width;
position = child.DesiredSize.Height;
height = Math.Max(height, child.DesiredSize.Height);
}
gapCoordinates[col] = width + columnWidth;
}
return new Size(width + GapWidth, height);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) {
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
UIElement element = visualAdded as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown += expandAtVisual;
}
element = visualRemoved as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown -= expandAtVisual;
}
}
private void expandAtVisual(object sender, MouseButtonEventArgs e) {
// find element column
int column = columns[(UIElement)sender];
GapWidth = 0;
GapColumn = column;
if (opened == sender) {
RaiseEvent(new RoutedEventArgs(CloseGapEvent, this));
}
opened = sender;
}
private void onPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private static void columnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((GapPanel)d).onPropertyChanged("GapX");
((GapPanel)d).RaiseEvent(new RoutedEventArgs(ColumnChangedEvent, d));
}
}
}
Leave a comment if there's anything you feel I need to explain.
Also, I can't help promoting the book "WPF4 Unleashed" by Adam Nathan. Much if not everything I managed to do above is explained in great detail in this book, so it's a great resource for anyone wanting to learn more about WPF.