Inspired by this post, I'm trying to use a TreeView inside the AutoCompleteBox's popup in order to show hierarchical data.
Here's what i did:
I Made my own TreeView that implements ISelectionAdapter (not sure it's perfect)
Edit AutoCompleteBox template and change the selector to my treeview
Try Bind my ViewModel to it
Unfortunately this is not working.
i checked my new TreeView with the same binding to the same object and it works prefectly when it's outside of a AutoCompleteBox template.
Any idea? any special way to implement ISelectionAdapter for treeview? did i missed something?
couldn't find any example for it on the web...
Thanks.
I managed to write it eventually.
Made a custom control for it. it still need to be refined, but it works good:
Generic.xaml :
<ControlTemplate x:Key="CommonValidationToolTipTemplate" TargetType="ToolTip">
<Grid x:Name="Root" Margin="5,0" Opacity="0" RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TranslateTransform x:Name="Translation" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="Translation">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="Translation"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="#052A2E31" CornerRadius="5" Margin="4,4,-4,-4"/>
<Border Background="#152A2E31" CornerRadius="4" Margin="3,3,-3,-3"/>
<Border Background="#252A2E31" CornerRadius="3" Margin="2,2,-2,-2"/>
<Border Background="#352A2E31" CornerRadius="2" Margin="1,1,-1,-1"/>
<Border Background="#FFDC000C" CornerRadius="2">
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" UseLayoutRounding="false"/>
</Border>
</Grid>
</ControlTemplate>
<Style TargetType="local:AutoCompleteTreeView"
xmlns:System="clr-namespace:System;assembly=mscorlib">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="#FFFFFFFF"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="MinWidth" Value="45"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoCompleteTreeView">
<Grid Opacity="{TemplateBinding Opacity}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PopupStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1" To="PopupOpened"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="PopupClosed"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="PopupOpened">
<Storyboard>
<DoubleAnimation To="1.0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PopupBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="PopupClosed">
<Storyboard>
<DoubleAnimation To="0.0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PopupBorder"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>True</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="Text" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" IsTabStop="True" Margin="0" Padding="{TemplateBinding Padding}" Style="{TemplateBinding TextBoxStyle}"/>
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource CommonValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>true</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
<Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
</Grid>
</Border>
<Popup x:Name="Popup">
<Grid Opacity="{TemplateBinding Opacity}">
<Border x:Name="PopupBorder" BorderThickness="0" Background="#11000000" HorizontalAlignment="Stretch" Opacity="0">
<Border.RenderTransform>
<TranslateTransform X="1" Y="1"/>
</Border.RenderTransform>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="0" HorizontalAlignment="Stretch" Opacity="1.0" Padding="0">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFDDDDDD" Offset="0"/>
<GradientStop Color="#AADDDDDD" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="-1" Y="-1"/>
</TransformGroup>
</Border.RenderTransform>
<local:TreeViewSelectionAdapter x:Name="SelectionAdapter" BorderThickness="0" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ItemTemplate="{TemplateBinding ItemTemplate}" ItemContainerStyle="{TemplateBinding ItemContainerStyle}" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</Border>
</Border>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
AutoCompleteTreeView.cs:
public class AutoCompleteTreeView : AutoCompleteBox
{
public AutoCompleteTreeView()
{
DefaultStyleKey = typeof(AutoCompleteTreeView);
}
}
TreeViewSelectionAdapter.cs:
public class TreeViewSelectionAdapter : TreeView, ISelectionAdapter
{
private object lastSelectedItem = null;
/// <summary>
///This prevents the text box of the AutoCompleteBox control from being updated continuously.
/// </summary>
private bool IgnoreAnySelection { get; set; }
/// <summary>
/// An event that indicates that a selection is complete and has been
/// made, effectively a commit action.
/// </summary>
public event RoutedEventHandler Commit;
/// <summary>
/// An event that indicates that the selection operation has been
/// canceled.
/// </summary>
public event RoutedEventHandler Cancel;
/// <summary>
/// Initializes a new instance of the SelectorSelectionAdapter class.
/// </summary>
public TreeViewSelectionAdapter()
{
base.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(OnSelectionChanged);
MouseLeftButtonUp += OnSelectorMouseLeftButtonUp;
}
void OnSelectionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (IgnoreAnySelection)
{
return;
}
SelectionChangedEventHandler handler = this.SelectionChanged;
if (handler != null)
{
IList oldSelectedItem = new List<object>();
if (lastSelectedItem != null)
oldSelectedItem.Add(lastSelectedItem);
if (SelectionChanged != null)
handler(this, new SelectionChangedEventArgs(oldSelectedItem, new List<object> { this.SelectedItem }));
lastSelectedItem = this.SelectedItem;
}
}
public new object SelectedItem
{
get
{
return base.SelectedItem;
}
set
{
this.SelectItem(value);
}
}
/// <summary>
/// Handles the mouse left button up event on the selector control.
/// </summary>
private void OnSelectorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IgnoreAnySelection = false;
OnSelectionChanged(this, null);
OnCommit(this, new RoutedEventArgs());
}
public event SelectionChangedEventHandler SelectionChanged;
/// <summary>
/// Gets or sets the items source.
/// </summary>
public new IEnumerable ItemsSource
{
get { return base.ItemsSource; }
set
{
if (base.ItemsSource != null)
{
INotifyCollectionChanged notify = base.ItemsSource as INotifyCollectionChanged;
if (notify != null)
{
notify.CollectionChanged -= OnCollectionChanged;
}
}
base.ItemsSource = value;
if (base.ItemsSource != null)
{
INotifyCollectionChanged notify = base.ItemsSource as INotifyCollectionChanged;
if (notify != null)
{
notify.CollectionChanged += OnCollectionChanged;
}
}
}
}
/// <summary>
/// Handles the CollectionChanged event, resetting the selection
/// ignore flag.
/// </summary>
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
IgnoreAnySelection = true;
}
/// <summary>
/// Process a key down event.
/// </summary>
public void HandleKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
IgnoreAnySelection = false;
OnCommit(this, e);
e.Handled = true;
break;
case Key.Down:
if ((ModifierKeys.Alt & Keyboard.Modifiers) == ModifierKeys.None)
{
IgnoreAnySelection = true;
//SelectedIndexIncrement();
TreeView tv = this.FindName("SelectionAdapter") as TreeView;
tv.KeyDown += new KeyEventHandler(tv_KeyDown);
tv.Focus();
List<TreeViewItem> ls = new List<TreeViewItem>(tv.GetContainers());
ls[0].Focus();
e.Handled = true;
}
break;
case Key.Escape:
OnCancel(this, e);
e.Handled = true;
break;
default:
break;
}
}
void tv_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
IgnoreAnySelection = false;
OnSelectionChanged(this, null);
OnCommit(this, new RoutedEventArgs());
break;
}
}
/// <summary>
/// Fires the Commit event.
/// </summary>
private void OnCommit(object sender, RoutedEventArgs e)
{
RoutedEventHandler handler = Commit;
if (handler != null)
{
handler(sender, e);
}
AfterAdapterAction();
}
/// <summary>
/// Fires the Cancel event.
/// </summary>
private void OnCancel(object sender, RoutedEventArgs e)
{
RoutedEventHandler handler = Cancel;
if (handler != null)
{
handler(sender, e);
}
AfterAdapterAction();
}
/// <summary>
/// Change the selection after the actions are complete.
/// </summary>
private void AfterAdapterAction()
{
this.SetSelectedContainer(null);
}
/// <summary>
/// Initializes a new instance of a DataGridAutomationPeer.
/// </summary>
public AutomationPeer CreateAutomationPeer()
{
return new TreeViewAutomationPeer(this);
}
}
Using the control:
<ccontrols:AutoCompleteTreeView x:Name="textbox" MinimumPrefixLength="1" IsEnabled="True" ItemsSource="{Binding MyNodes}" >
<ccontrols:AutoCompleteTreeView.ItemTemplate>
<hirarch:HierarchicalDataTemplate ItemsSource="{Binding MyNodes}" >
<TextBlock Text="{Binding Name}" />
</hirarch:HierarchicalDataTemplate>
</ccontrols:AutoCompleteTreeView.ItemTemplate>
</ccontrols:AutoCompleteTreeView>
The hierarchy objects:
public class Node
{
public string Name { get; set; }
public List<Node> MyNodes { get; set; }
public override string ToString()
{
return Name;
}
}
Related
I’m new to WPF NotifyIcon and I’m trying to use the Windowless Sample which uses a ResourceDictionary instead of a window and the TaskbarIcon.DataContext is set to my ViewModel. I can call the example commands (ShowWindowCommand, etc.) and it works fine.
However, in my ViewModel I can’t figure out how to reference the TaskbarIcon. I want to show a standard balloon something like NotifyIcon.ShowBallonTip(title, text, BalloonIcon.Error). I’ve tried giving the tb:TaskbarIcon an x:Name but my ViewModel still does not see it.
How do I reference the TaskbarIcon from my ViewModel? Thanks!
<tb:TaskbarIcon x:Key="NotifyIcon"
IconSource="/Red.ico"
ToolTipText="Double-click for window, right-click for menu"
DoubleClickCommand="{Binding ShowWindowCommand}"
ContextMenu="{StaticResource SysTrayMenu}">
<tb:TaskbarIcon.DataContext>
<local:NotifyIconViewModel />
</tb:TaskbarIcon.DataContext>
</tb:TaskbarIcon>
In MVVM you can't directly operate control in your VM.
VM must nothing know about View. Instead of it you must define property Icon in VM (as I see it is NotifyIconViewModel) and then in View (TaskbarIcon) you need bind it to IconSource.
VM:
public Icon { get { new System.Drawing.Icon(#"..\Properties\Icons\YourIcon.ico"); }};
View
<tb:TaskbarIcon DataContext="YourViewModel"
IconSource="{Binding Path=Icon}"
ToolTipText="Double-click for window, right-click for menu"
DoubleClickCommand="{Binding ShowWindowCommand}"
ContextMenu="{StaticResource SysTrayMenu}"/>
Here is one implementation that works fine for me...
MainWindow.xaml:
xmlns:tb="http://www.hardcodet.net/taskbar"
<tb:TaskbarIcon x:Name="TrayIcon" IconSource="icon.ico" ToolTipText="{Binding TrayIconText}" MenuActivation="RightClick"
LeftClickCommand="{Binding TrayClickCommand}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Exit" Command="{Binding CloseAppCommand}" CommandParameter="{Binding}">
<MenuItem.Icon>
<Image Source="pack://application:,,,/Resources/Img/Shutdown32.png" Style="{StaticResource MenuItemImage}"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
MainWindow.xaml.cs:
//Subscribe to VM event:
var mvm = DataContext as MainViewModel;
mvm.DisplayTrayBalloonNotice += OnDisplayTrayBalloon;
private void OnDisplayTrayBalloon(object sender, NotificationEventArgs<string> e)
{
var balloon = new TrayBalloon { NotificationText = e.Data };
PopupAnimation pa = PopupAnimation.Fade;
TrayIcon.ShowCustomBalloon(balloon, pa, Settings.Default.NotificationDuration * 1000);
}
TrayBalloon.xaml:
<UserControl x:Class="MCPublisher.Resources.TrayBalloon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar"
Height="150" Width="300">
<UserControl.Resources>
<Storyboard x:Key="FadeIn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Grid"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0" />
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.95" />
<SplineDoubleKeyFrame KeyTime="00:00:03" Value="0.95" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HighlightCloseButton">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ImgClose"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.4" />
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeCloseButton">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ImgClose"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1" />
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.4" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeBack">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Grid"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1" />
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOut" Completed="OnFadeOutCompleted">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Grid"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1" />
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.2" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="tb:TaskbarIcon.BalloonShowing">
<BeginStoryboard Storyboard="{StaticResource FadeIn}" x:Name="FadeInBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="ImgClose">
<BeginStoryboard Storyboard="{StaticResource HighlightCloseButton}" x:Name="HighlightCloseButtonBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="ImgClose">
<BeginStoryboard Storyboard="{StaticResource FadeCloseButton}" x:Name="FadeCloseButtonBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<StopStoryboard BeginStoryboardName="FadeInBeginStoryboard" />
<BeginStoryboard x:Name="FadeBackBeginStoryboard1" Storyboard="{StaticResource FadeBack}" />
</EventTrigger>
<EventTrigger RoutedEvent="tb:TaskbarIcon.BalloonClosing">
<BeginStoryboard Storyboard="{StaticResource FadeOut}" x:Name="FadeOutBeginStoryboard" />
</EventTrigger>
</UserControl.Triggers>
<Grid x:Name="Grid" MouseEnter="Grid_OnMouseEnter" MouseLeave="Grid_OnMouseLeave">
<Border x:Name="BnBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="1" CornerRadius="5"
BorderBrush="{DynamicResource GridView_FilteringControlOuterBorder}"
Background="{DynamicResource GridView_FilteringControlBackground}">
<Border.Effect>
<DropShadowEffect Color="#FF747474" />
</Border.Effect>
</Border>
<Image HorizontalAlignment="Left" Margin="10 10 0 0" Width="64" Height="64" Stretch="Fill" VerticalAlignment="Top"
Source="{Binding ImageSource}" />
<TextBlock Margin="84 35 10 0" VerticalAlignment="Top" FontSize="16" FontWeight="Bold" Foreground="#FF575757">
<Run Text="VHI Notification"/>
</TextBlock>
<Path Fill="#FFFFFFFF" Stretch="Fill" Margin="84 60 10 0" VerticalAlignment="Top" Height="1"
Data="M26,107 L220.04123,107" SnapsToDevicePixels="True">
<Path.Stroke>
<LinearGradientBrush EndPoint="0.973,0.5" StartPoint="0.005,0.5">
<GradientStop Color="#FF6868FF" Offset="0" />
<GradientStop Color="#346868FF" Offset="1" />
</LinearGradientBrush>
</Path.Stroke>
</Path>
<TextBlock Margin="20 90 10 0" VerticalAlignment="Top" Height="24" TextWrapping="Wrap" FontSize="12" FontWeight="Bold">
<Run Text="⦁ "/>
<Run Text="{Binding NotificationText}"/>
</TextBlock>
<Image x:Name="ImgClose" HorizontalAlignment="Right" Margin="0 10 10 0" VerticalAlignment="Top" Width="16" Height="16"
Stretch="Fill" Opacity="0.4" ToolTip="Close Balloon" Source="pack://application:,,,/Resources/Img/Exit32.png"
MouseDown="ImgClose_OnMouseDown" />
</Grid>
</UserControl>
TrayBalloon.xaml.cs:
public partial class TrayBalloon : UserControl, INotifyPropertyChanged
{
private bool isClosing;
public TrayBalloon()
{
InitializeComponent();
DataContext = this;
TaskbarIcon.AddBalloonClosingHandler(this, OnBalloonClosing);
}
private string imageSource;
public string ImageSource
{
get { return imageSource; }
set
{
imageSource = value;
OnPropertyChanged("ImageSource");
}
}
private string notificationText;
public string NotificationText
{
get { return notificationText; }
set
{
notificationText = value;
OnPropertyChanged("NotificationText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnBalloonClosing(object sender, RoutedEventArgs e)
{
e.Handled = true;
isClosing = true;
}
private void OnFadeOutCompleted(object sender, EventArgs e)
{
var pp = (Popup)Parent;
pp.IsOpen = false;
}
private void Grid_OnMouseEnter(object sender, MouseEventArgs e)
{
if (isClosing) return;
var taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this);
taskbarIcon.ResetBalloonCloseTimer();
}
private void Grid_OnMouseLeave(object sender, MouseEventArgs e)
{
if (isClosing) return;
var taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this);
taskbarIcon.CloseBalloon();
}
private void ImgClose_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this);
taskbarIcon.CloseBalloon();
}
}
I'm not sure if this is the preferred way to accomplish this in a ViewModel but this is how I got a reference to the TaskbarIcon.
tb = (TaskbarIcon)Application.Current.FindResource("MyTray");
Here's the overview :
I am using a Custom control (CusCtrl) to show taskbar icon , it also has a Popup property. so when you click on the icon the CusCtrl shows the Popup.
I am setting the child of the pop up with a UserControl (lets say UC1).
I am setting the DataContext of CusCtrl with a ViewModel thus even the UC1 get binded with a respective ViewModel (lets say VM1)
Now the UC1 has some element - a Label, on clicking the label I need 2 things to happen:
Invoke a command on the view model VM1 -
From the command I need to pass some of the view model's properties as parameters and open some window UI.
Close the PopUp -
For this I have thought of listening the MouseUp Event in code behind of UserControl & then fire a routed event (FirePopUpClose - this event is defined in the UserControl UC1) which will be handled by the app & then from within the
handler, Custom Conntrol's ClosePopUp method will be called.
I do know how to invoke command on the MouseUp event on label using the Interactivity dll, but then how can I raise the FirePopUpClose routed Event?
Or how do apply a MouseUp event handler on label as well as bind a command to that label ?
Am I even thinking this the right way or there's some better cleaner way to do some UI action as well as close the PopUp by sticking to MVVM?
what about the next solution; try to use Popup.IsOpen property (here is the information about, and here is the example of usage #867 – Controlling Whether a Popup Is Open Using Data Binding ). Bind it to UC1 DataContext directly or via DependencyProperty of CusCtrl user control (you have to create that property in your control that encapsulate the Popup). That's all, this way you will be able to manage popup to be opened or closed without the event.
Update
Try the next:
1. Main Xaml:
<Window x:Class="PopupIsOpenDataBindingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:popupIsOpenDataBindingHelpAttempt="clr-namespace:PopupIsOpenDataBindingHelpAttempt"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<popupIsOpenDataBindingHelpAttempt:DemoMainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="PopupInnerControlDataTemplateKey" DataType="{x:Type popupIsOpenDataBindingHelpAttempt:TaskBarDemoViewModel}">
<Grid Width="150" Height="85">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextString}"
Margin="5" Foreground="Red"
Width="150" TextWrapping="WrapWithOverflow"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
<Button Grid.Row="1" Content="Press to close" Command="{Binding PopupInnerButtonCommand}"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
<Image x:Key="ImageControl" Source="Pic/2015_10_16_Bing_en-US.jpg" IsHitTestVisible="False"/>
</Window.Resources>
<Grid Width="75" Height="75" HorizontalAlignment="Center" VerticalAlignment="Center">
<popupIsOpenDataBindingHelpAttempt:TaskBarIconProjectDemo
ButtonContentProperty="{StaticResource ImageControl}"
ButtonCommandProperty="{Binding ShowPopupCommand}"
PopupIsOpenProperty="{Binding IsPopupOpen, UpdateSourceTrigger=PropertyChanged}"
PopupInnerContentControlDataContext="{Binding TaskBarDemoViewModel, UpdateSourceTrigger=PropertyChanged}"
PopupInnerContentControlContentTemplate="{StaticResource PopupInnerControlDataTemplateKey}"/>
</Grid>
2. Popup encapsulating XAMl:
<UserControl x:Class="PopupIsOpenDataBindingHelpAttempt.TaskBarIconProjectDemo"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="This">
<Grid>
<Button Style="{StaticResource SpecialButtonStyle}" Command="{Binding ElementName=This, Path=ButtonCommandProperty}"
Content="{Binding ElementName=This, Path=ButtonContentProperty}"></Button>
<Popup IsOpen="{Binding ElementName=This, Path=PopupIsOpenProperty}">
<ContentControl Content="{Binding ElementName=This, Path=PopupInnerContentControlDataContext}"
ContentTemplate="{Binding ElementName=This, Path=PopupInnerContentControlContentTemplate}"/>
</Popup>
</Grid>
3. Popup encapsulating control code behind (dependency properties):
public partial class TaskBarIconProjectDemo : UserControl
{
public static readonly DependencyProperty ButtonCommandPropertyProperty = DependencyProperty.Register("ButtonCommandProperty", typeof (ICommand), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty PopupIsOpenPropertyProperty = DependencyProperty.Register("PopupIsOpenProperty", typeof (bool), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(bool)));
public static readonly DependencyProperty PopupInnerContentControlDataContextProperty = DependencyProperty.Register("PopupInnerContentControlDataContext", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
public static readonly DependencyProperty PopupInnerContentControlContentTemplateProperty = DependencyProperty.Register("PopupInnerContentControlContentTemplate", typeof (DataTemplate), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(DataTemplate)));
public static readonly DependencyProperty ButtonContentPropertyProperty = DependencyProperty.Register("ButtonContentProperty", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
public TaskBarIconProjectDemo()
{
InitializeComponent();
}
public ICommand ButtonCommandProperty
{
get { return (ICommand) GetValue(ButtonCommandPropertyProperty); }
set { SetValue(ButtonCommandPropertyProperty, value); }
}
public bool PopupIsOpenProperty
{
get { return (bool) GetValue(PopupIsOpenPropertyProperty); }
set { SetValue(PopupIsOpenPropertyProperty, value); }
}
public object PopupInnerContentControlDataContext
{
get { return (object) GetValue(PopupInnerContentControlDataContextProperty); }
set { SetValue(PopupInnerContentControlDataContextProperty, value); }
}
public DataTemplate PopupInnerContentControlContentTemplate
{
get { return (DataTemplate) GetValue(PopupInnerContentControlContentTemplateProperty); }
set { SetValue(PopupInnerContentControlContentTemplateProperty, value); }
}
public object ButtonContentProperty
{
get { return (object) GetValue(ButtonContentPropertyProperty); }
set { SetValue(ButtonContentPropertyProperty, value); }
}
}
4. View Models:
public class DemoMainViewModel:BaseObservableObject
{
private bool _isOpen;
private TaskBarDemoViewModel _taskBarDemoViewModel;
private ICommand _showPopupCommnad;
public DemoMainViewModel()
{
TaskBarDemoViewModel = new TaskBarDemoViewModel(ClosePopup, "Here you can put your content. Go for it...");
}
private void ClosePopup()
{
IsPopupOpen = false;
}
public bool IsPopupOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
OnPropertyChanged();
}
}
public TaskBarDemoViewModel TaskBarDemoViewModel
{
get { return _taskBarDemoViewModel; }
set
{
_taskBarDemoViewModel = value;
OnPropertyChanged();
}
}
public ICommand ShowPopupCommand
{
get { return _showPopupCommnad ?? (_showPopupCommnad = new RelayCommand(ShowPopup)); }
}
private void ShowPopup()
{
IsPopupOpen = true;
}
}
public class TaskBarDemoViewModel:BaseObservableObject
{
private readonly Action _closePopupCommand;
private ICommand _command;
private string _textString;
public TaskBarDemoViewModel(Action closePopupCommand, string content)
{
_closePopupCommand = closePopupCommand;
TextString = content;
}
public ICommand PopupInnerButtonCommand
{
get { return _command ?? (_command = new RelayCommand(TargetMethod)); }
}
private void TargetMethod()
{
//add your logic here
if(_closePopupCommand == null) return;
_closePopupCommand();
}
public string TextString
{
get { return _textString; }
set
{
_textString = value;
OnPropertyChanged();
}
}
}
5. Button style (change this as your need):
<Color x:Key="ButtonLowerPartKey">#FFD5E0EE</Color>
<Color x:Key="ButtonUpperPartKey">#FFEAF1F8</Color>
<Color x:Key="PressedColorButtonLowerPartKey">#FFF4C661</Color>
<Color x:Key="PressedButtonUpperPartKey">#FFF4CC87</Color>
<Color x:Key="HooveredButtonLowerPartKey">#FFFFD06D</Color>
<Color x:Key="HooveredButtonUpperPartKey">#FFFFF0DF</Color>
<Style x:Key="SpecialButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Padding" Value="5">
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="Grid">
<Ellipse x:Name="ButtonControlBorder" Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Ellipse.Fill>
<LinearGradientBrush x:Name="BrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource ButtonLowerPartKey}" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse x:Name="Pressed" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Opacity="0">
<Ellipse.Fill>
<LinearGradientBrush x:Name="PressedBrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource PressedColorButtonLowerPartKey}" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse x:Name="InnerPressed"
Width="{Binding ElementName=Pressed, Path=Width}" Height="{Binding ElementName=Pressed, Path=Height}"
Stroke="DarkOrange" Opacity="0" StrokeThickness="1" SnapsToDevicePixels="True" Fill="Transparent"/>
<ContentPresenter Content="{TemplateBinding Button.Content}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.OpacityMask>
<VisualBrush Visual="{Binding ElementName=ButtonControlBorder}" />
</ContentPresenter.OpacityMask>
</ContentPresenter>
<Grid.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard x:Name="MouseEnterStoryboard">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource ButtonUpperPartKey}" To="{StaticResource HooveredButtonUpperPartKey}" Duration="0:0:0.3" AutoReverse="False" />
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource ButtonLowerPartKey}" To="{StaticResource HooveredButtonLowerPartKey}" Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource HooveredButtonUpperPartKey}" To="{StaticResource ButtonUpperPartKey}" Duration="0:0:1" AutoReverse="False" />
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource HooveredButtonLowerPartKey}" To="{StaticResource ButtonLowerPartKey}" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
<ControlTemplate.Resources>
<Storyboard x:Key="MouseUpTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MouseDownTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="0.8" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="InnerPressedMouseUpTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="InnerPressedMouseDownTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" SourceName="Grid" Value="True">
<Setter Property="Stroke" TargetName="ButtonControlBorder">
<Setter.Value>
<SolidColorBrush Color="{StaticResource HooveredButtonLowerPartKey}">
</SolidColorBrush>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ButtonBase.IsPressed" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MouseDownTimeLine}" />
<BeginStoryboard Storyboard="{StaticResource InnerPressedMouseDownTimeLine}">
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource MouseUpTimeLine}" />
<BeginStoryboard Storyboard="{StaticResource InnerPressedMouseUpTimeLine}">
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
BaseObservableObject is the simple implementation of INCP.
RelayCommand is the simple implementation of ICommand interface.
I'll glad to help if you will have the problem with the code.
Regards,
Value="{TemplateBinding HeaderColor}"I've created my own control, and I'm wondering if I can bind a Border.Background to a template property. Currently i'm setting it with a StaticResource like the following:
<Color x:Key="ControlMouseOverColor">green</Color>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:6" Value="{StaticResource ControlMouseOverColor}" />
</ColorAnimationUsingKeyFrames>
I'd like it be a property on my control, and be able to set it as a template binding
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:6" Value="{TemplateBinding HeaderColor}" />
</ColorAnimationUsingKeyFrames>
MainPage.xaml
<ctrl:Selection Grid.Column="0" HeaderColor="Red" HeaderText="Header Text" />
my class:
public static readonly DependencyProperty HeaderColorProperty =
DependencyProperty.Register("HeaderColor", typeof(System.Windows.Media.Color), typeof(Selection), new PropertyMetadata(System.Windows.Media.Colors.Red));
public System.Windows.Media.Color HeaderColor {
get { return (System.Windows.Media.Color)GetValue(HeaderColorProperty); }
set { SetValue(HeaderColorProperty, value); }
}
This 2nd option does not work, should I be able to do this? I don't get an error, it just does not change to the color I set.
Comment left by AngelWPF asked for more code, pasting below, I'm in the beginning stages of learning to create a control, wanted to note that, because there is a lot I have not got to yet, one piece at a time :)
generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SelectionControl.Library"
xmlns:ctrl="clr-namespace:SelectionControl.Library;assembly=SelectionControl">
<LinearGradientBrush x:Key="HeaderBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Gray" Offset="1" />
</LinearGradientBrush>
<Color x:Key="ControlMouseEnterColor">aliceblue</Color>
<Color x:Key="ControlMouseLeaveColor">Gray</Color>
<Color x:Key="ControlLeftMouseUpColor">Red</Color>
<Style TargetType="ctrl:Selection">
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="Auto" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="AliceBlue" />
<Setter Property="Margin" Value="2,2,2,2" />
<Setter Property="Background" Value="{StaticResource ResourceKey=HeaderBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:Selection">
<Grid x:Name="RootElement" Margin="{TemplateBinding Margin}">
<!-- Visual States -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="MouseEnter">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:.5" Value="{TemplateBinding HeaderColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseLeave">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:1" Value="{StaticResource ControlMouseLeaveColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseLeftUp">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:1" Value="{StaticResource ControlLeftMouseUpColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- End Visual States-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header -->
<Border x:Name="headerLayout" Background="{TemplateBinding Background}" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="2,2,2,2" BorderBrush="Black" BorderThickness="1">
<StackPanel>
<ToggleButton ></ToggleButton>
<TextBlock Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding HeaderText}" FontWeight="{TemplateBinding FontWeight}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
<!-- Body Content -->
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Selection.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SelectionControl.Library {
[TemplateVisualState(Name = Selection.MouseEnterStateName, GroupName = Selection.CommonStatesGroupName)]
[TemplateVisualState(Name = Selection.MouseLeaveStateName, GroupName = Selection.CommonStatesGroupName)]
[TemplateVisualState(Name = Selection.MouseLeftUpStateName, GroupName = Selection.CommonStatesGroupName)]
public class Selection : ContentControl {
public const string CommonStatesGroupName = "CommonStates";
public const string MouseEnterStateName = "MouseEnter";
public const string MouseLeaveStateName = "MouseLeave";
public const string MouseLeftUpStateName = "MouseLeftUp";
public Selection() {
this.DefaultStyleKey = typeof(Selection);
this.MouseEnter += new MouseEventHandler(OnMouseEnter);
this.MouseLeave += new MouseEventHandler(OnMouseLeave);
this.MouseLeftButtonUp += new MouseButtonEventHandler(OnMouseLeftButtonUp);
}
void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
this.GoToState(Selection.MouseLeftUpStateName, true);
}
void OnMouseLeave(object sender, MouseEventArgs e) {
this.GoToState(Selection.MouseLeaveStateName, true);
}
void OnMouseEnter(object sender, MouseEventArgs e) {
this.GoToState(Selection.MouseEnterStateName, true);
}
private void GoToState(string stateName, bool useTransitions) {
VisualStateManager.GoToState(this, stateName, useTransitions);
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(Selection), new PropertyMetadata(""));
public string HeaderText {
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty HeaderColorProperty =
DependencyProperty.Register("HeaderColor", typeof(System.Windows.Media.Color), typeof(Selection), new PropertyMetadata(System.Windows.Media.Colors.Red));
public System.Windows.Media.Color HeaderColor {
get { return (System.Windows.Media.Color)GetValue(HeaderColorProperty); }
set { SetValue(HeaderColorProperty, value); }
}
}}
I have had mixed results using TemplateBinding on custom dependency properties. Because of this, I have used RelativeSource TemplatedParent which seems to work in every situation.
<EasingColorKeyFrame KeyTime="0:0:.5" Value="{Binding HeaderColor, RelativeSource={RelativeSource TemplatedParent}}" />
I am trying to implement the following hierarchy of controls:
RoundedHandleBase:
- this will have a round border around a content presenter that will host a path defined in the template of the child controls.
- it will also provide 2 states: MouseOver and Normal (it displays a rectangle over the path when mouse is over)
public class RoundedHandleBase : ContentControl
{
public RoundedHandleBase()
: base()
{
base.DefaultStyleKey = typeof(RoundedHandleBase);
this.MouseEnter += new MouseEventHandler(RoundedHandleBase_MouseEnter);
this.MouseLeave += new MouseEventHandler(RoundedHandleBase_MouseLeave);
}
void RoundedHandleBase_MouseEnter(object sender, MouseEventArgs e)
{
bool bRet = VisualStateManager.GoToState((Control)sender, "MouseOver", true);
}
void RoundedHandleBase_MouseLeave(object sender, MouseEventArgs e)
{
bool bRet = VisualStateManager.GoToState((Control)sender, "Normal", true);
}
}
FlipHandle:
- this defines a Path as the template (that is the actual icon of the handle):
public class FlipHandle : RoundedHandleBase
{
private bool bFlipPath;
public bool FlipPath
{
get { return bFlipPath; }
set
{
bFlipPath = value;
}
}
public FlipHandle()
: base()
{
base.DefaultStyleKey = typeof(FlipHandle);
bFlipPath = false;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (bFlipPath)
{
ContentPresenter content = this.GetTemplateChild("content") as ContentPresenter;
Path arrowPath = this.GetTemplateChild("Path_FlipHandle") as Path;
}
}
}
generic.xaml
*generic.xaml*
<!--ROUNDED HANDLES -->
<ControlTemplate x:Key="RoundedHandleBaseTemplate" TargetType="local:RoundedHandleBase">
<Viewbox>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangleOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.6"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="0.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="rectangleOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.15"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border CornerRadius="2,2,2,2">
<Grid Height="Auto" Width="Auto">
<Border Margin="0,0,0,0" BorderBrush="#FFFFFFFF" BorderThickness="2,2,2,2" CornerRadius="1,1,1,1" Width="20" Height="20">
<Grid>
<Viewbox Width="14" Height="14">
<ContentPresenter x:Name="content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter>
</Viewbox>
<Rectangle x:Name="rectangleOver" Fill="#FFFFFFFF" RadiusX="2" RadiusY="2" Margin="1,1,1,1" Opacity="0"/>
</Grid>
</Border>
</Grid>
</Border>
</Viewbox>
</ControlTemplate>
<Style TargetType="local:RoundedHandleBase" x:Key="RoundedHandleStyle">
<Setter Property="Template" Value="{StaticResource RoundedHandleBaseTemplate}">
</Setter>
</Style>
<Style TargetType="local:FlipHandle" BasedOn="{StaticResource RoundedHandleStyle}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Path x:Name="Path_FlipHandle" Width="47.5646" Height="49.2324" Canvas.Left="472.184" Canvas.Top="584.534" Stretch="Fill" Fill="#FFFFFFFF" Data="M 494.718,633.76C 508.77,634.067 520.133,622.527 519.738,608.563C 519.534,601.327 516.378,595.502 512.286,591.527C 508.354,587.711 502.225,583.955 493.832,584.608C 482.414,585.496 475.704,591.912 472.184,601.111L 501.638,601.111C 501.668,599.118 501.62,595.698 501.638,593.124C 505.862,596.766 509.822,601.13 513.884,605.191C 514.376,605.684 517.992,608.638 517.965,609.272C 517.962,609.336 516.478,610.758 516.013,611.224C 513.753,613.483 511.846,615.214 509.802,617.258C 506.906,620.154 504.43,623.172 501.638,625.066C 501.618,622.551 501.669,619.192 501.638,617.258L 472.184,617.258C 475.668,626.822 483.53,633.515 494.718,633.76 Z ">
<Path.RenderTransform>
<ScaleTransform CenterX="23.7823" CenterY="24.6162" ScaleX="1" ScaleY="1"></ScaleTransform>
</Path.RenderTransform>
</Path>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!--/ROUNDED HANDLES -->
My problem is:
- I cannot access the Path in the FlipHandle in order to modify its ScaleTransform in OnApplyTemplate.
Path arrowPath = this.GetTemplateChild("Path_FlipHandle") as Path;
-> arrowPath is allways null.
On the other hand, i can access the "content" content presenter:
ContentPresenter content = this.GetTemplateChild("content") as ContentPresenter;
-> the above works.
Please advice.
Thank you.
You can't access path with GetTemplateChild if it is not part of the template. You could make it part of your template or you could define some visual states on your flip handle and use GoToState method.
Hey SO, got a question about the TreeView control in Silverlight.
I have an application which dynamically adds elements to a treeview. Some of the elements are long enough to require horizontal scrolling. When they are added to the treeview, my treeview remains correctly all the way scrolled left so you have to scroll to see the end of the item. However, if I click on one of my items (which the hides the treeview), and then use the 'back to results' button I implemented (note this only deals with visibility changes) the treeview becomes visible and it is automatically scrolled to the center.
Does anyone know how I can get the treeview to scroll all the way left when I hit back to results?
I've tried messing with the treeview template:
<Style TargetType="controls:TreeView" x:Name="SCREW">
<Setter Property="Background" Value="#FFFFFFFF" />
<Setter Property="Foreground" Value="#FF000000" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="1" />
<Setter Property="BorderBrush" Value="#FF000000" />
<Setter Property="IsTabStop" Value="True" />
<Setter Property="TabNavigation" Value="Once" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:TreeView" x:Name="SCREWTEMPLATE">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Unfocused" />
<VisualState x:Name="Focused" />
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Validation" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Validation" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationToolTip" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>True</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" Margin="1">
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="{x:Null}" BorderBrush="Transparent" BorderThickness="0" IsTabStop="False" TabNavigation="Once" Loaded="ScrollViewer_Loaded">
<ItemsPresenter Margin="5" />
</ScrollViewer>
</Border>
</Border>
<Border x:Name="Validation" Grid.Column="1" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="#FFDB000C" CornerRadius="2" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="ValidationToolTip" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" IsHitTestVisible="True" />
</ToolTipService.ToolTip>
<Grid Width="10" Height="10" HorizontalAlignment="Right" Margin="0,-4,-4,0" VerticalAlignment="Top" Background="Transparent">
<Path Margin="-1,3,0,0" Fill="#FFDC000C" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 Z" />
<Path Margin="-1,3,0,0" Fill="#FFFFFFFF" Data="M 0,0 L2,0 L 8,6 L8,8" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But the problem with that is I don't know how to access the ScrollViewer from the code behind... so I can't call ScrollView.setScrollOffset(0d,0d) or anything like that.
Any ideas? Thanks a million.
One last thing, I'd like to try to avoid implementing a new control that extends treeview. I'm really hoping there is a way to access/modify and use functions associated with the control template from c# codebehind.
I'd just set up an attached property for this and create the logic you want in there. Then you would decorate your treeview with the attached property. We do something similar with other controls that contain scrollviewers:
public class ScrollResetService
{
public static DependencyProperty IsScrollResetProperty = DependencyProperty.RegisterAttached("IsScrollReset",
typeof(bool),
typeof(ScrollResetService),
new PropertyMetadata(false,
OnIsScrollResetChanged));
public static void SetIsScrollReset(DependencyObject d, bool value)
{
d.SetValue(IsScrollResetProperty, value);
}
public static bool GetIsScrollReset(DependencyObject d)
{
return d.GetValue(IsScrollResetProperty) == null ? false : (bool)d.GetValue(IsScrollResetProperty);
}
private static void OnIsScrollResetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
bool isScrollReset;
if (e.NewValue!= null && bool.TryParse(e.NewValue.ToString(), out isScrollReset) && treeView != null)
{
treeView.SelectedItemChanged += (sender, args) =>
{
var scrolls =
treeView.GetAllLogicalChildrenOfType<IScrollInfo>();
scrolls.ForEach(i => i.SetVerticalOffset(0));
};
}
}
}
public static class Extensions
{
public static IEnumerable<T> GetAllLogicalChildrenOfType<T>(this FrameworkElement parent)
{
Debug.Assert(parent != null, "The parent cannot be null.");
return parent.GetVisualChildren().Flatten(item => item.GetVisualChildren()).OfType<T>();
}
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> childSelector)
{
if (items == null) return Enumerable.Empty<T>();
return items.Concat(items.SelectMany(i => childSelector(i).Flatten(childSelector)));
}
internal static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int counter = 0; counter < childCount; counter++)
{
yield return VisualTreeHelper.GetChild(parent, counter);
}
}
}
I put this code into a behavior (would also work in code behind) after handling the SelectedItemChanged event of the TreeView:
var offset = this.HorizontalScrollOffsetAfterSelect; // would be 0 if you don't want to make that adjustable
if (!Double.IsNaN(offset))
{
var scrollViewer = trv.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset(offset);
// and because that wasn't enough because of timing issues:
var scrollBar = scrollViewer.GetVisualDescendants().OfType<ScrollBar>().Where(cur => cur.Orientation == Orientation.Horizontal).FirstOrDefault();
if (scrollBar != null)
{
RoutedPropertyChangedEventHandler<double> handler = null;
handler = (sender, e) =>
{
scrollBar.ValueChanged -= handler;
scrollViewer.ScrollToHorizontalOffset(offset);
};
scrollBar.ValueChanged += handler;
}
}
}
Requires using System.Linq; and using System.Windows.Controls.Primitives; and the Toolkit ofc.
Works well enough for me so far.