If I change StaysOpen to "True", the popup shows up, but it doesn't close when you click outside of it, so that's not what I want.
Here is the relevant XAML code:
<Border x:Name="popupPlacementTarget">
<i:Interaction.Behaviors>
<local:PopupBehavior>
<local:PopupBehavior.Popup>
<Popup PlacementTarget="{Binding ElementName=popupPlacementTarget}"
Placement="MousePoint"
StaysOpen="False">
<ContentPresenter Content="{Binding SomeContent}" ContentTemplate="{StaticResource SomeContentTemplate}" />
</Popup>
<local:PopupBehavior.Popup>
</local:PopupBehavior>
</i:Interaction.Behaviors>
</Border>
And here is the PopupBehavior code:
public class PopupBehavior : Behavior<UIElement>
{
#region Dependency Properties
public static readonly DependencyProperty PopupProperty = DependencyProperty.Register(
"Popup", typeof(Popup), typeof(PopupBehavior), new FrameworkPropertyMetadata(default(Popup)));
#endregion Dependency Properties
#region Properties
public Popup Popup
{
get { return (Popup)this.GetValue(PopupProperty); }
set { this.SetValue(PopupProperty, value); }
}
#endregion Properties
#region Protected Methods
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseDown += this.OnMouseDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseDown -= this.OnMouseDown;
}
#endregion Protected Methods
#region Private Methods
private void OnMouseDown(object sender, MouseButtonEventArgs eventArgs)
{
var popup = this.Popup;
if (popup == null)
{
return;
}
this.Popup.IsOpen = true;
}
#endregion Private Methods
}
Any idea why my popup won't show up with StaysOpen="False"?
It turns out there was an ancestor that was indiscriminately grabbing capture on mouse down. Everything is working correctly now that I've corrected this issue.
As an aside, I was able to hack around this problem by setting StaysOpen="True", inheriting Popup, and hooking in to the global mouse events within the derived Popup. When the popup opens, I'd attach the handler to the global input events. Then, when an event is received, I'd filter it such that I'm only responding to left mouse button down events. In handling this event, I'd close the popup and detach the event if the mouse is not hovering the popup. It worked, but it is obviously a dirty hack, and I'm happy that I got this working without it.
Related
I have a custom control with bool property. The property is binding include template control with Popup.
XAML control:
<controls:AutoCompleteTextBox x:Name="PART_Editor"
IsEnabled="False"
IsPopupOpen="{Binding IsAutocompletePopupOpen}" />
Property in the control:
public bool IsPopupOpen
{
get => (bool)GetValue(IsPopupOpenProperty);
set => SetValue(IsPopupOpenProperty, value);
}
public static readonly DependencyProperty IsPopupOpenProperty =
DependencyProperty.Register("IsPopupOpen", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Binding to element include in template control:
<Popup x:Name="PART_AutoCompletePopup"
IsOpen="{Binding IsPopupOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
I need change property IsPopupOpen on click. I decided to do this in behavior, but I need my control to be disabled. Therefore I was add behavior to container of control
<Grid>
<controls:AutoCompleteTextBox x:Name="PART_Editor"
IsEnabled="False"
IsPopupOpen="{Binding IsAutocompletePopupOpen}"/>
<i:Interaction.Behaviors>
<behaviors:PopupContainerBehavior IsPopupOpen="{Binding IsAutocompletePopupOpen, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</Grid>
Behavior code:
public class PopupContainerBehavior : Behavior<UIElement>
{
public bool IsPopupOpen
{
get { return (bool)GetValue(IsPopupOpenProperty); }
set { SetValue(IsPopupOpenProperty, value); }
}
public static readonly DependencyProperty IsPopupOpenProperty =
DependencyProperty.Register("IsPopupOpen", typeof(bool), typeof(PopupContainerBehavior), new PropertyMetadata(false));
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= OnMouseLeftButtonUp;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IsPopupOpen = true;
}
}
The problem is that the property first changes to true and then immediately changes to false.Through SNOOP you can see this by the flashing value of the property.I think the problem lies in TwoWay Binding, but I don't know how to fix it
The reason why this is happening is due to mouse capture.
The behavior's PreviewMouseLeftButtonDown event handler tells the Popup to open
The Popup captures the mouse
The rest of the click events fire for the container
The container takes away mouse capture from the Popup
The Popup immediately closes
This can be a tricky problem to solve.
You might want to consider making your control able to open the Popup when it is disabled, vs. trying to do it from the outside. Even though a control is disabled, you can make parts of it clickable by setting IsHitTestVisible on the parts you need to be interactive, etc.
I have added a click event on the calendar control. But with my implementation, this event don't work.
My code in Cal.cs control:
#region click
public static RoutedEvent ClickEvent =
EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Cal));
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
protected virtual void OnClick()
{
RoutedEventArgs args = new RoutedEventArgs(ClickEvent, this);
RaiseEvent(args);
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
OnClick();
}
#endregion
XAML code :
<Calen:Cal x:Name="Calendar" Margin="0,50,0,0" Click="Calendar_Click"/>
C# code :
private void Calendar_Click(object sender, RoutedEventArgs e)
{
string t = "";
}
I don't found any solution. I don't know why this code don't work correctly.
Can you help me with this problem please ?
You need to set the DataContext to point to the class that contains the code behind.
<UserControl>
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</UserControl>
This is necessary because unfortunately, by default, the DataContext is not set up correctly in WPF.
For more info on DataContext, see ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext".
I have the following UserControl that has an attached behavior on it. At the moment I have to select one of the DataGrid items in order for it to run the behavior.
Only guessing here but it's kind of understandable because these containers are not focusable or are not supposed to register keyboard events, so it goes to the next child available???
I could place this functionality on the window however this UserControl can get swapped out with another UserControl and I prefer for it not be be lying around.
This is just a unique case in my app where the only thing on the window is the DataGrid and I don't necessarily want to select a DataGrid item to begin the behavior.
My question is: Is it possible to get the following behavior to work? Either on the UserControl, Grid or maybe some other control that would permit this.
<UserControl x:Class="UserManagement.LaunchPad"
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:helpers="clr-namespace:UserManagement.Helpers"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<i:Interaction.Behaviors>
<helpers:OpenPopupBehaviors FindPopupObject="{Binding ElementName=PopupFind}"
GotoPopupObject="{Binding ElementName=PopupGoto}" />
</i:Interaction.Behaviors>
<Grid>
<DockPanel>
<DataGrid />
</DockPanel>
<Popup Name="PopupGoto" />
<Popup Name="PopupFilter "/>
</Grid>
</UserControl>
Here is the behavior:
class OpenPopupBehaviors : Behavior<UserControl>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.KeyDown += _KeyBoardBehaviorKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.KeyDown -= _KeyBoardBehaviorKeyDown;
}
void _KeyBoardBehaviorKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F && (Keyboard.Modifiers & (ModifierKeys.Control)) == (ModifierKeys.Control))
{
var popup = FindPopupObject as UserManagement.Controls.NonTopmostPopup;
if (popup == null)
return;
popup.IsOpen = true;
}
if (e.Key == Key.G && (Keyboard.Modifiers & (ModifierKeys.Control)) == (ModifierKeys.Control))
{
var popup = GotoPopupObject as Popup;
if (popup == null)
return;
popup.IsOpen = true;
}
}
public static readonly DependencyProperty FindPopupObjectProperty =
DependencyProperty.RegisterAttached("FindPopupObject", typeof(object), typeof(OpenPopupBehaviors), new UIPropertyMetadata(null));
public object FindPopupObject
{
get { return (object)GetValue(FindPopupObjectProperty); }
set { SetValue(FindPopupObjectProperty, value); }
}
public static readonly DependencyProperty GotoPopupObjectProperty =
DependencyProperty.RegisterAttached("GotoPopupObject", typeof(object), typeof(OpenPopupBehaviors), new UIPropertyMetadata(null));
public object GotoPopupObject
{
get { return (object)GetValue(GotoPopupObjectProperty); }
set { SetValue(GotoPopupObjectProperty, value); }
}
}
So far I can see is that the focus if your issue
you can make use of Preview events to receive the events even if the focus is on control's child or their descendants
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewKeyDown += _KeyBoardBehaviorKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewKeyDown-= _KeyBoardBehaviorKeyDown;
}
I have changed KeyDown to PreviewKeyDown
above solution will work as long as the AssociatedObject have focus on it or any child within it. if you look forward to control it globally then perhaps you may need to attach the events to top level elements eg. Window
so in order to do that you need to find the appropriate parent of AssociatedObject and attach the event to the same.
also make sure that the following cast is valid
FindPopupObject as UserManagement.Controls.NonTopmostPopup;
it could be
FindPopupObject as Popup;
Here's the problem:
I have a 'home button' with a listener
private void Item_Tap(object sender, TappedRoutedEventArgs e)
{
if (this.Frame != null)
{
this.Frame.Navigate(typeof(Home));
}
}
If I built a User Control, using the 'home button' inside it, how can I accomplish the same navigation?
** I'm near to the solution**.
This is the user control:
public sealed partial class TopBarControl : UserControl
{
public Frame Frame { get; set; }
public object Parameter { get; set; }
public TopBarControl(Frame frame)
{
this.InitializeComponent();
this.Frame = frame;
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
var dateAndTime = DateTime.Now;
Date.Text = dateAndTime.Date.ToString("MM/dd/yyyy");
User.Text = localSettings.Values["userName"].ToString();
//SectionTitle.Text = Parameter.ToString();
}
private void GoHome_Tap(object sender, TappedRoutedEventArgs e)
{
if (this.Frame != null)
{
this.Frame.Navigate(typeof(Main_Menu));
}
}
}
on the xaml user control I have:
<Button Grid.Column="0" HorizontalAlignment="Right" BorderThickness="0">
<Image Source="/Assets/home_icon.png" Tapped="GoHome_Tap"/>
</Button>
Page associated to the user control
public WantedVehicles()
{
this.InitializeComponent();
}
private TopBarControl topBarControl;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Tit.Text = e.Parameter.ToString();
topBarControl.Frame = this.Frame;
topBarControl.Parameter = e.Parameter;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
}
protected override void SaveState(Dictionary<String, Object> pageState)
{
}
But the button doesn't work yet.
I understand your problem. Here's a simple way to create a home button that you can add anywhere in your application and it will navigate home. No need to cascade events or anything like that. Just create a custom control. This is different than a user control. This is just sub-classing the button control. Far more clean an approach.
Three easy steps.
Note: My custom control is going to be called HomeButton and my home page class is going to be called HomePage. Other than that, this should all be out-of-the-box for you.
First, define how your button will look:
<!-- custom home button -->
<Style TargetType="local:HomeButton" BasedOn="{StaticResource BackButtonStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Height="48" Width="48">
<Ellipse Stroke="White" StrokeThickness="2" />
<TextBlock Text="" FontSize="20"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Second, define your custom control like this:
public sealed class HomeButton : Button
{
public HomeButton()
{
this.DefaultStyleKey = this.GetType();
this.Click += HomeButton_Click;
}
void HomeButton_Click(object sender, RoutedEventArgs e)
{
var _Frame = Window.Current.Content as Frame;
var _Type = typeof(HomePage);
_Frame.Navigate(_Type);
}
}
Third, and finally, add your home button to your app!
<local:HomeButton HorizontalAlignment="Left" VerticalAlignment="Top" Margin="40" />
Here's how it looks:
It even works! :) If you want to style the button more, it will be easy to steal the styling of the BackButtonStyle in your /Common/StandardStyles.xaml file. Then you can have the same View States and all. Anyway, this will get you where you want.
So, this is home button that solves the problem you described in your question. It even solves it in a way that is less problematic than the solution you assumed you would get - since manually bubbling events across contexts can cause nightmares.
I hope this helps.
Add a button to your user control, and attach Item_Tap event handler to it.
<UserControl>
....
<Button Tapped="GoHome_Tap">Go Home</Button>
.....
</UserControl>
Add Frame to MyUserControl.cs. See your other question to know how to set the Frame property from enclosing Page.
public TopBarControl:UserControl
{
public Frame Frame {get;set;} // To be set in either OnNavigatedTo, or in Constructor.
}
Add Event Handler to MyUserControl.cs
private void GoHome_Tap(object sender, TappedRoutedEventArgs e)
{
if (this.Frame != null)
{
this.Frame.Navigate(typeof(Home));
}
}
You have to add an event to your UserControl that is fired in the button's event handler. The event that you posted above would still be defined outside of your user control unless you passed whatever "this" was into the UserControl's constructor.
I have a problem with LostFocus event it does not fire when I click on the background.I read some stuff about focus logic and keyboard focus but I could not find a way to get the focus from a control a like textbox when there is only one of them
XAML:
<Grid Height="500" Width="500">
<TextBox Height="23" Width="120" Margin="12,12,0,0" Name="textBox1" LostFocus="textBox1_LostFocus" />
</Grid>
C#:
private void textBox1_LostFocus(object sender, RoutedEventArgs e)
{
}
You must use the following tunnelling event : PreviewLostKeyboardFocus on your textbox
Tunneling: Initially, event handlers at the element tree root are
invoked. The routed event then travels a route through successive
child elements along the route, towards the node element that is the
routed event source (the element that raised the routed event).
Tunneling routed events are often used or handled as part of the
compositing for a control, such that events from composite parts can
be deliberately suppressed or replaced by events that are specific to
the complete control. Input events provided in WPF often come
implemented as a tunneling/bubbling pair. Tunneling events are also
sometimes referred to as Preview events, because of a naming
convention that is used for the pairs.
The following behavior will fix this:
public class TextBoxUpdateOnLostKeyboardFocusBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
if (AssociatedObject != null)
{
base.OnAttached();
AssociatedObject.LostKeyboardFocus += OnKeyboardLostFocus;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.LostKeyboardFocus -= OnKeyboardLostFocus;
base.OnDetaching();
}
}
private void OnKeyboardLostFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null && e.NewFocus == null)
{
// Focus on the closest focusable ancestor
FrameworkElement parent = (FrameworkElement) textBox.Parent;
while (parent is IInputElement && !((IInputElement) parent).Focusable)
{
parent = (FrameworkElement) parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent);
}
}
}
You can attach it to your TextBox as follows:
<TextBox>
<i:Interaction.Behaviors>
<behaviors1:TextBoxUpdateOnLostKeyboardFocusBehavior />
</i:Interaction.Behaviors>
</TextBox>