Is faking a WPF mouseover possible? - wpf

I have a data template with a textbox and a button with some styles on it. I would like to have the button show the mouse over state when focus is on the textbox beside it. Is this possible?
I figure it would involve something like this. I can get the textbox through use of FindVisualChild and FindName. Then I can set the GotFocus event on the textbox to do something.
_myTextBox.GotFocus += new RoutedEventHandler(TB_GotFocus);
Here in TB_GotFocus I'm stuck. I can get the button I want to show the mouse over state of, but I don't know what event to send to it. MouseEnterEvent isn't allowed.
void TB_GotFocus(object sender, RoutedEventArgs e)
{
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(this.DataTemplateInstance);
DataTemplate template = myContentPresenter.ContentTemplate;
Button _button= template.FindName("TemplateButton", myContentPresenter) as Button;
_button.RaiseEvent(new RoutedEventArgs(Button.MouseEnterEvent));
}

I don't think it's possible to fake the event but you can force the button to render itself as if it had MouseOver.
private void tb_GotFocus(object sender, RoutedEventArgs e)
{
// ButtonChrome is the first child of button
DependencyObject chrome = VisualTreeHelper.GetChild(button, 0);
chrome.SetValue(Microsoft.Windows.Themes.ButtonChrome.RenderMouseOverProperty, true);
}
private void tb_LostFocus(object sender, RoutedEventArgs e)
{
// ButtonChrome is the first child of button
DependencyObject chrome = VisualTreeHelper.GetChild(button, 0);
chrome.ClearValue(Microsoft.Windows.Themes.ButtonChrome.RenderMouseOverProperty);
}
you need to reference PresentationFramework.Aero.dlll for this to work and then it will only work on Vista for the Aero theme.
If you want it to work for other themes you should make a custom controltemplate for each of the theme you want to support.
See http://blogs.msdn.com/llobo/archive/2006/07/12/663653.aspx for tips

As a follow up to jesperll's comment, I think you can get around making a custom template for each theme by dynamically setting the style to the one you want / null.
Here is my window, with the style defined (but not set to anything).
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="MouseOverStyle">
<Setter Property="Background">
<Setter.Value>Green</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="MyTextBox" Grid.Column="0" Text="Some Text" Margin="2" GotFocus="TextBox_GotFocus" LostFocus="MyTextBox_LostFocus"/>
<Button x:Name="MyButton" Grid.Column="1" Content="Button" Margin="2" MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave" />
</Grid>
Instead of setting the style via triggers in the template, you can use events in your .cs file like so:
...
public partial class Window1 : Window
{
Style mouseOverStyle;
public Window1()
{
InitializeComponent();
mouseOverStyle = (Style)FindResource("MouseOverStyle");
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e) { MyButton.Style = mouseOverStyle; }
private void MyTextBox_LostFocus(object sender, RoutedEventArgs e) { MyButton.Style = null; }
private void Button_MouseEnter(object sender, MouseEventArgs e) { ((Button)sender).Style = mouseOverStyle; }
private void Button_MouseLeave(object sender, MouseEventArgs e) { ((Button)sender).Style = null; }
}
You get a reference to the style in the constructor and then dynamically set it / unset it. This way, you can define what you want your style to look like in Xaml, and you don't have to rely on any new dependencies.

Related

Custom Popup control not closing

I'm trying to create a custom dropdown control that acts like a ComboBox, such that the Popup opens when you click mouse down (not up), and closes when you click outside of the control.
The problem is that it only behaves if I set ClickMode to "Release". But what I really want is ClickMode="Press", such that the Popup opens on MouseDown instead of MouseUp.
But when I set it to ClickMode="Press", the popup won't close when you click outside the control.
Any ideas how I can achieve this?
Usage :
<StackPanel>
<local:CustomDropdown Width="200"
Height="50"
Content="Custom!" />
<ComboBox Width="200"
Margin="20">
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>C</ComboBoxItem>
</ComboBox>
</StackPanel>
Class :
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
Xaml :
<Style TargetType="{x:Type local:CustomDropdown}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<ToggleButton IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Popup StaysOpen="False"
Placement="Bottom"
IsOpen="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1"
Padding="50">
<TextBlock Text="Popup!" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
If you want it to work as expected with ClickMode.Press, you should programmatically set the IsOpen property to false whenever you want to the close the Popup. For example whenever you detect a click outside of the ToggleButton.
You could for example handle the PreviewMouseLeftButtonDown event for the parent window in your control. Something like this:
internal class CustomDropdown : ContentControl
{
private ToggleButton _toggleButton;
public CustomDropdown()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_toggleButton = GetTemplateChild("toggleButton") as ToggleButton;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown += OnWindowPreviewMouseLeftButtonDown;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown -= OnWindowPreviewMouseLeftButtonDown;
}
private void OnWindowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ToggleButton toggleButton = FindParent<ToggleButton>(e.OriginalSource as DependencyObject);
if (toggleButton != _toggleButton)
IsOpen = false;
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
}
XAML:
<ControlTemplate>
<Grid>
<ToggleButton x:Name="toggleButton" ...
You already have a working answer. However, finding the parent Window and parent ToggleButton can impact performance (depending on the depth of the visual tree).
As an alternative solution I suggest to focus on handling the Popup instead.
There are two conditions that prevent the Popup from closing itself: the button is configured with the ButtonBase.ClickMode set to ClickMode.Pressed AND the user is not clicking anything focusable inside the Popup.
If one of those two conditions evaluates to false (=> ClickMode.Release or the user has moved focus inside the Popup) your code will work as you would expected it to work.
Note that in order to allow the user to move focus inside the Popup, there must be a child that is focusable (UIElement.Focusable is set to true - it's false by default for most controls that don't require user interaction). For example, TextBlock is not focusable by default.
Because you want to keep the button configured to raise the Click event on mouse button press, you have to move the focus manually. But when you set it manually, the Popup won't receive a mouse click to setup itself to watch the focus. Therefore, you will end up closing the Popup manually (taking away the related control from the Popup).
The following example closes the Popup by observing the Mouse.PreviewMouseDownOutsideCapturedElement event to identify when the focus has moved away from the CustomDropdown control (mouse click outside the Popup):
CustomDropdown.cs
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
"IsOpen",
typeof(bool),
typeof(CustomDropdown),
new PropertyMetadata(default(bool), OnIsOpenChanged));
public CustomDropdown()
{
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElement);
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isOpen = (bool)e.NewValue;
if (isOpen)
{
_ = Mouse.Capture(d as IInputElement, CaptureMode.SubTree);
}
else
{
_ = Mouse.Capture(null);
}
}
// Manually close the Popup if click is recorded outside the CustomDropdown/Popup
private void OnPreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e)
{
SetCurrentValue(IsOpenProperty, false);
}
}

WPF UserControl generic click event

I have created a UserControl in WPF which consists of 2 simple buttons in the first run.
Now, I want to display in a MessageBox the x:Name of the button which is clicked by the user, but I don't want to create a Clicked event for each button separately.
Is it possible to program 1 generic Clicked event in the UserControl and then identify the sender object to get the correct x:Name ?
Is it possible to program 1 generic Clicked event in the UserControl and then identify the sender object to get the correct x:Name ?
Sure:
<Button x:Name="first" Click="generic_Click" />
<Button x:Name="second" Click="generic_Click" />
private void generic_Click(object sender, RoutedEventArgs e)
{
Button clickedButton = sender as Button;
MessageBox.Show(clickedButton.Name);
}
Use an EventSetter for that in a style for a button.
Example in xaml:
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.EventOvw2"
Name="dpanel2"
Initialized="PrimeHandledToo"
>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="b1SetColor"/>
</Style>
</StackPanel.Resources>
<Button>Click me</Button>
<Button Name="ThisButton" Click="HandleThis">
Raise event, handle it, use handled=true handler to get it anyway.
</Button>
</StackPanel>
And then in cs file:
void b1SetColor(object sender, RoutedEventArgs e)
{
Button b = e.Source as Button;
b.Background = new SolidColorBrush(Colors.Azure);
}
void HandleThis(object sender, RoutedEventArgs e)
{
e.Handled=true;
}

How can I place one control in a callout that orginates from another control in XAML?

Here is a setup: I have a textbox with a numberic value. According to the requirements every time anybody changes that value an accompanying comment needs to be provided. So visually there must be another textbox for the comment that should be displayed right next to the first one. Ideally the comment textbox needs to be placed in a callout that originates from the value textbox and displayed on the right from it overlaying anything what's underneath of it just like on this picture:
I know how to do easily it in CSS and HTML.
I have to do the same in Silverlight now.
Unfortunately I am not very strong in it, so what I am specifically asking about is how having 2 textboxes make one of them appear next to another (on the right overlaying whatever controls are underneath it) with as less XAML and code as possible.
Use a ToolTip, and set the Placement such that it appears to the right. in XAML, you can template your ToolTip to look however you want, even if that means mimicking the TextBox appearance.
This is the purpose of the ToolTip, and I feel strongly that you should always use the right tool for the right job. :)
I hope this helps. Let us know if you need code samples.
EDIT: Added the following code samples:
<TextBox ToolTipService.Placement="Right">
<ToolTipService.ToolTip>
<TextBox Text="{Binding CalloutText, Mode=OneWay}" IsReadOnly="True"/>
</ToolTipService.ToolTip>
</TextBox>
Ok, I ended up writing my own behaviour
namespace MyNamespace
{
public class CommentBehavior : Behavior<TextBox>
{
private readonly TimeSpan howLongWeWaitBeforePopupCloses = TimeSpan.FromMilliseconds(200);
private DispatcherTimer popupClosingTimer;
public static DependencyProperty PopupProperty = DependencyProperty.Register("Popup", typeof(Popup), typeof(CommentBehavior), new PropertyMetadata(null));
public Popup Popup
{
get { return (Popup)this.GetValue(PopupProperty); }
set { this.SetValue(PopupProperty, value); }
}
protected override void OnAttached()
{
this.popupClosingTimer = new DispatcherTimer();
this.popupClosingTimer.Stop();
this.popupClosingTimer.Interval = howLongWeWaitBeforePopupCloses;
this.popupClosingTimer.Tick += this.ClosePopup;
this.AssociatedObject.GotFocus += this.GotFocus;
this.AssociatedObject.LostFocus += this.LostFocus;
this.Popup.Child.GotFocus += PopupChild_GotFocus;
this.Popup.Child.LostFocus += PopupChild_LostFocus;
}
private void PopupChild_LostFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Start();
}
private void PopupChild_GotFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Stop();
}
protected override void OnDetaching()
{
this.AssociatedObject.GotFocus -= this.GotFocus;
this.AssociatedObject.LostFocus -= this.LostFocus;
this.Popup.GotFocus -= PopupChild_GotFocus;
this.popupClosingTimer.Tick -= this.ClosePopup;
this.popupClosingTimer = null;
}
private void ClosePopup(object sender, EventArgs e)
{
this.Popup.IsOpen = false;
this.popupClosingTimer.Stop();
}
protected void GotFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Stop();
this.Popup.IsOpen = true;
var at = this.CalculatePopupPosition();
this.Popup.HorizontalOffset = at.X;
this.Popup.VerticalOffset = at.Y;
}
private Point CalculatePopupPosition()
{
var owner = this.AssociatedObject;
var transformation = owner.TransformToVisual(Application.Current.RootVisual);
var at = transformation.Transform(new Point(owner.ActualWidth, 0));
return at;
}
protected void LostFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Start();
}
}
}
And the following XAML
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Background="Red">
<TextBox Width="200" Text="0.01">
<i:Interaction.Behaviors>
<local:CommentBehavior>
<local:CommentBehavior.Popup>
<Popup>
<TextBox Text="Comment" />
</Popup>
</local:CommentBehavior.Popup>
</local:CommentBehavior>
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>
</Grid>

Dynamically change the width of a WPF window when content gets visible

I want to be able to hide the left hand side of the screen and right hand side of the screen at the beginning of the program.
Then when the user presses create new button the left hand side of the screen becomes available so they can create the new item. Then when they press save it comes back to the middle datagrid only.
Then I want to add an event when the double click on the datagrid row (data is programmed in to the datagrid in the code) the right hand side of the screen becomes visible then when the button allocate is pressed the right hand side disappears again just leaving the datagrid.
I am fairly new to WPF so unsure whether this can be done or not. I am trying to do it in the same window at the moment I am making prototypes for my company and already have some that use separate windows. I had posted an image but am un able to post it as I am a new user.
To hide and show controls I would either recommend using expanders (as your comment says you've done) or Grids and setting their visibility as needed. If you want your side panels to appear over the datagrid then displaying them is just a matter of changing their visibility. If don't want obscure the DataGrid then you will need to change the visibility of the panels as well as the size of the window.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Grid>
<!-- DataGrid display -->
<Grid>
<StackPanel>
<Button Content="Add New" Click="OnAddNewButtonClick" Width="100"/>
<DataGrid ItemsSource="{Binding GridItems}" IsReadOnly="True" Name="dataGrid">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="OnRowDoubleClick"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</StackPanel>
</Grid>
<!-- Left column pops up over DataGrid -->
<Grid Name="LeftColumn" Visibility="Collapsed" Background="Red" Width="200" HorizontalAlignment="Left">
<StackPanel VerticalAlignment="Center">
<Button Content="Hide Column" Click="OnLeftColumnButtonClick"/>
</StackPanel>
</Grid>
<!-- Right Column expands screen size-->
<Grid Visibility="Collapsed" Name="RightColumn" Width="200" HorizontalAlignment="Right">
<StackPanel Background="Green" >
<TextBlock Text="Hidden Column"/>
<Button Content="Hide Panel" Click="OnRightColumnButtonClick"/>
</StackPanel>
</Grid>
</Grid>
</Window
C# - I know you're working in VB, but this was quicker for me. The code should be fairly self explanatory, but if you need a VB sample let me know:
public partial class MainWindow : Window
{
public ObservableCollection<Person> GridItems { get; set; }
private const double CollapsedWidth = 500;
private const double ExpandedWidth = 700;
public MainWindow()
{
DataContext = this;
GridItems = new ObservableCollection<Person>();
GridItems.Add(new Person { Name = "Foo", Age = 1 });
GridItems.Add(new Person { Name = "Bar", Age = 2 });
InitializeComponent();
Width = CollapsedWidth;
}
private void OnAddNewButtonClick(object sender, RoutedEventArgs e)
{
LeftColumn.Visibility = Visibility.Visible;
}
private void OnLeftColumnButtonClick(object sender, RoutedEventArgs e)
{
LeftColumn.Visibility = Visibility.Collapsed;
}
private void OnRowDoubleClick(object sender, MouseButtonEventArgs e)
{
Width = ExpandedWidth;
RightColumn.Visibility = Visibility.Visible;
}
private void OnRightColumnButtonClick(object sender, RoutedEventArgs e)
{
RightColumn.Visibility = Visibility.Collapsed;
Width = CollapsedWidth;
}
}

Why does my popup not respond to touches? [duplicate]

This question already has answers here:
SurfaceScrollViewer: getting touch on nested children
(2 answers)
Closed 2 years ago.
I have been banging my head on this for a while now! Here is my simple User Control:
<UserControl x:Class="DaCapo.MyUserControl"
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"
xmlns:s="http://schemas.microsoft.com/surface/2008"
IsManipulationEnabled="True"
Width="300" Height="300">
<Canvas>
<s:SurfaceButton x:Name="button" Width="100" Height="100" Content="Click!" Style="{x:Null}"/>
<Popup x:Name="popup" Width="200" Height="100" IsOpen="False" StaysOpen="True" PlacementRectangle="0,0,200,100"
AllowsTransparency="True" Focusable="True">
<DockPanel x:Name="dockpanel" Width="200" Height="100" Background="SteelBlue" Focusable="True"/>
</Popup>
</Canvas>
</UserControl>
I want to be able to detect touches on the DockPanel or in a possible child of it. Here follows the code behind for the same class, with the alternatives I attempted:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
TouchExtensions.AddHoldGestureHandler(this.button, this.HoldHandler);
/* NONE OF THE FOLLOWING WORKS */
// TouchExtensions.AddTapGestureHandler(this.popup, this.TapHandler);
// this.dockpanel.TouchDown += new System.EventHandler<TouchEventArgs>(popup_TouchDown);
// this.popup.TouchDown += new System.EventHandler<TouchEventArgs>(popup_TouchDown);
// this.popup.ManipulationStarting += new System.EventHandler<ManipulationStartingEventArgs>(popup_ManipulationStarting);
// this.dockpanel.ManipulationStarting += new System.EventHandler<ManipulationStartingEventArgs>(popup_ManipulationStarting);
}
void popup_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { Debug.WriteLine("Tap..."); }
void popup_TouchDown(object sender, TouchEventArgs e) { Debug.WriteLine("Tap..."); }
private void TapHandler(object sender, TouchEventArgs e) { Debug.WriteLine("Tap..."); }
private void HoldHandler(object sender, TouchEventArgs e) { Debug.WriteLine("Holding..."); this.popup.IsOpen = true; }
}
I do believe I am missing something obvious. Can someone please help me? Thanks.
The Button & popup needs to be connected to its click (or touch) handlers defined in the code behind, in XAML itself.
If you want to handle touch events for the dockpanel, How about
adding a button inside the dockpanel with opacity = 0 ??
EDIT :
I can see that you defined a few handlers, but did you add those Handlers to the button?
For example, for a SurfaceButton :
IN XAML :
<s:SurfaceButton Click="OpenButton_Click"/>
Correspondingly connects the function in C# as:
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
// Handle the action for the click here.
}
Under the covers, Popup creates another hwnd to render its content into. This is different from all other WPF controls. You need to register this hwnd with the Surface SDK so it will start sending touch events to it. Use this to do that: http://msdn.microsoft.com/en-us/library/microsoft.surface.presentation.input.touchextensions.enablesurfaceinput.aspx

Resources