I have a DataTemplate that is used by a ListBox to provide the 'look' for each ListBoxItem. In addition and because these ListBoxItems can be wider than the column in which they're held, I also have a Popup that is drawn over the top of the ListBoxItem if the mouse is over this item.
The Popup uses the same ContentTemplate as the item it covers and therefore the appearance is that the item under the mouse simply stops clipping to the bounds of the ListBox it resides in. It works well except I cannot successfully pass mouse events back to the control it is obscuring, so that the underlying control can detect a drag operation starting.
<!--This datatemplate is used to create a popup over the listboxitem in ItemsView.xaml-->
<DataTemplate x:Key="PopupDaybookItemDataTemplate">
<StackPanel x:Name="pdidt">
<Popup x:Name="myPopup" PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Placement="Relative" StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=myPopup, Path=IsMouseOver}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsOpen" Value="True" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Mode=OneWay, ElementName=pdidt, Path=IsMouseOver}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsOpen" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<ListBoxItem Style="{StaticResource DayBookItemStyle}"
PreviewMouseDown="ItemListPopup_OnMouseButtonEvent"
MouseDown="ItemListPopup_OnMouseButtonEvent"
PreviewMouseMove="ItemListPopup_OnMouseEvent"
MouseMove="ItemListPopup_OnMouseEvent"
>
<ContentPresenter ContentTemplate="{StaticResource DaybookItemDataTemplate}"
PreviewMouseDown="ItemListPopup_OnMouseButtonEvent"
MouseDown="ItemListPopup_OnMouseButtonEvent"
PreviewMouseMove="ItemListPopup_OnMouseEvent"
MouseMove="ItemListPopup_OnMouseEvent"
/>
</ListBoxItem>
</Popup>
<ContentPresenter x:Name="MyItemPresenter" ContentTemplate="{StaticResource DaybookItemDataTemplate}" ></ContentPresenter>
</StackPanel>
</DataTemplate>
Notice the events tied to both the ListBoxItem and the ContentPresenter within the Popup. They are tied to the following code-behind:
private void ItemListPopup_OnMouseButtonEvent(object sender, MouseButtonEventArgs e)
{
Popup popup = (((DependencyObject)sender).GetVisualParent<Popup>());
popup.PlacementTarget.RaiseEvent(e);
}
private void ItemListPopup_OnMouseEvent(object sender, MouseEventArgs e)
{
Popup popup = (((DependencyObject)sender).GetVisualParent<Popup>());
popup.PlacementTarget.RaiseEvent(e);
}
The PlacementTarget is the ListBoxItem that is being drawn over.
The MouseDown on the ContentPresenter is what allows the underlying ListViewItem to know it has been clicked and therefore selected, and that is working fine. The MouseDown on the ListBoxItem does not, against my expectations. (whether or not the ContentPresenter MouseDown event is hooked).
The other events seem to perform no useful task but I include them because I think they're an obvious thing to try.
I expected that relaying the MouseDown and MouseMove events to the underlying ListBox would allow it to detect a drag operation but it does not. I have also tried passing ALL mouse events in this way (tunnelling and bubbling) but drag operations still do not start.
The underlying control detects drag operations perfectly if the Popup is (permanently) disabled, however disabling the Popup within the mouse event does not help.
I have tried setting the Popup IsHitTestVisible to false but that doesn't help - I guess because the Popup is a separate Window and not part of the Visual Tree therefore there isn't anywhere for the events to bubble up to?
I have also tried changing the mouseEvent.Source but I get a runtime error "Cannot change the RoutedEvent property while the event is being routed". I tried to 'fake' a routed event but couldn't work out how to.
Any help appreciated. Oh and I'm new here so if I break etiquette please let me know.
Related
I attempted to follow a pattern described in an answer to this question.
How to set focus to textbox using MVVM?
However, I am having trouble with the concept of keyboard focus. If I have notepad or some other application running at the same time as my WPF application and click on notepad to put the keyboard focus there, then do something to cause my other application to put the focus into one of its text boxes, then the trigger gives the visual cue that my application's text box now has the keyboard focus. However when I start typing I can see that is not the case because the text is actually going into notepad.
Here is the xaml for my trigger.
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ReadyForDataEntry}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
</DataTrigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="Background" Value="Lavender"/>
<Setter Property="BorderBrush" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
Essentially the textbox will sometimes light up with the border and background color indicating that IsKeyboardFocused = true for that textbox, even though keyboard entry will be received by whatever application (e.g., one note, notepad) was last clicked in. What am I missing? Why would that WPF control have IsKeyboardFocused set true when the keyboard focus is clearly not true at all?
You're not doing anything wrong; this is a known quirk of WPF.
When a control receives logical focus, WPF attempts to give it keyboard focus as well. However, when you assign keyboard focus in an inactive WPF application, the application behaves as if it's active. That means, among other things, a focused TextBox will show a blinking caret, and IsKeyboardFocused and related properties will be set.
I have seen this issue in the past, and it was trivial to reproduce.
Xaml:
<Window x:Class="WpfTest.FocusTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBox x:Name="_textBox"
Width="150">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocused"
Value="true">
<Setter Property="Background"
Value="Lavender" />
<Setter Property="BorderBrush"
Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Margin="0,7,0,0"
Content="_Click Me"
Click="OnButtonClick" />
</StackPanel>
</Grid>
</Window>
Code behind:
public partial class FocusTest
{
public FocusTest()
{
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
_textBox.Text = "";
// NOTE: Requires System.Reactive.Core, available in NuGet.
System.Reactive.Concurrency.Scheduler.Default.Schedule(
TimeSpan.FromSeconds(5),
() => this.Dispatcher.BeginInvoke(new Action(this.SetFocus)));
}
private void SetFocus()
{
_textBox.Text = "Focused";
FocusManager.SetFocusedElement(_textBox, _textBox);
}
}
Hit the button, Alt+Tab over to Notepad, and wait 5 seconds for the TextBox to receive focus. The IsKeyboardFocused trigger is fired, and the blinking caret appears, but keyboard input is still sent to Notepad.
The key point here is that the problem only arises when an element is given focus while another application is active (hence the artificial delay). Note that the problem still arises if you replace the SetFocusedElement call with Keyboard.Focus(_textBox), _textBox.Focus(), and other variations.
Unfortunately, I am not aware of a reliable, non-hacky way of fixing this issue. I don't recall how much time I spent on it, but I ultimately decided it wasn't worth the trouble. It's just not something that comes up that often.
I have a MVVM WPF app in Visual Studio 2008 and NET Framework 3.5 SP1. Among other controls, this app have a datagrid and a button.
Button:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" FocusManager.FocusedElement="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myDataGrid, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image VerticalAlignment="Center" Source="/MyWPFApp;component/Images/Calculate.png"></Image>
<TextBlock VerticalAlignment="Center">Calculate</TextBlock>
</StackPanel>
</Button>
What I am trying to do is to set focus on the current selected item in the datagrid once button is clicked so when I click on up/down arrow key in the keyboard I can move to any other item in the datagrid.
So I have tried to set FocusManager.FocusedElement property in the button but it is not working. I have also tried to set as is without specifying the Path:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
In the first attempt, without setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
when I click on down of up arrow key in the keyboard (after clicking button), it change the focus to another control in the UI that is not the current selected item in the datagrid.
In the second attempt, setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid, Path=SelectedItem}"
it simply does nothing, neither no focus in the current selected item in the datagrid nor in any other control.
Also I have tried an attached behaviour as said here but it is not working:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" classes:EventFocusAttachment.ElementToFocus="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
Another attempt:
It works on second key click, first click is ignored.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (myDataGrid.SelectedIndex > -1)
{
var selectedRow = (Microsoft.Windows.Controls.DataGridRow)myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
FocusManager.SetIsFocusScope(selectedRow, true);
FocusManager.SetFocusedElement(selectedRow, selectedRow);
}
}
#Evk Solution works perfectly.
The following should work:
if (myDataGrid.SelectedIndex > -1) {
var container = (DataGridRow) myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
if (container != null) {
container.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
You can put that code into Button.Click event (nothing wrong here, what we are doing is completely view-only thing) or if you don't like code-behind you can create attached property\behavior from that.
I'm trying to have a popup open up when the textbox gains keyboard focus. When the user clicks off the popup, I want the popup to close.
From what I can gather StaysOpen="False" makes the popup close when the user clicks off of it. The problem is that it doesn't work together with the IsKeyboardFocused = true datatrigger and causes the popup to not show up at all. If I remove the StaysOpen property, the popup shows up, but closes when it is clicked (Even though I have no code specifying a closed event).
Here is what it looks like without the StaysOpen property.
Here is my code. Any help is much appreciated thanks!
<TextBox x:Name="SearchTextBox"
Height="26"
Width="165"
TextWrapping="Wrap"
Text="Example">
</TextBox>
<Popup
Placement="Bottom"
PlacementTarget="{Binding ElementName=SearchTextBox}"
Height="200"
Width="100"
StaysOpen="False"> <!-- If StaysOpen property is removed, popup displays!!! -->
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Setter Property="IsOpen" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SearchTextBox, Path=IsKeyboardFocused}"
Value="true">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
</Popup>
I have a UserControl that I'm writing that will have a TextBox and Button. When the user clicks the button, I'd like the current date/time to be placed into the TextBox. The code below has two issues:
For some reason the binding is throwing an exception "Two-way binding requires Path or XPath".
If I just set the value to something like "Test", then it works but only while the button itself is pressed, once I lift the mouse button the text goes away.
In essence, I'd like the value to be set at mouse click not during. I'd like to keep this pure xaml if possible, but I understand if it needs to be in the code-behind. Any help would be appreciated!
<Button x:Name="ClockGo">
<Image Source="/Best.Controls;component/Resources/clock_go.png" />
</Button>
<TextBox HorizontalAlignment="Stretch">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ClockGo, Path=IsPressed}" Value="True">
<Setter Property="Text" Value="{Binding Source={x:Static sys:DateTime.Today}, StringFormat='{}{0:MM/dd/yyyy}'}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
You should be able to get rid of "Two-way binding requires Path or XPath" exception by setting the binding mode to OneWay. Textbox has a two way binding by default and I believe the exception is due to the value conversion.
<Setter Property="Text" Value="{Binding Source={x:Static sys:DateTime.Now}, StringFormat='{}{0:MM/dd/yyyy hh:mm:ss}', Mode=OneWay}" />
On your second point where the value only shown when the button is pressed and goes away after button click is released, this is the expected behaviour. The Trigger Setter only apply when the binding condition is true, it will be revert to default value when the condition no longer true.
Please refer to MSDN site on the remark section which spells out above behaviour: http://msdn.microsoft.com/en-us/library/system.windows.trigger.aspx
Unless you have a very strong reason to stick with XAML based solution, code behind seems to be the better option here instead of tweaking with triggers.
Currently, I have the following datatrigger:
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ScheduleDataGrid, Path=HasItems}"
Value="false">
<Setter Property="Button.IsEnabled" Value="false"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
and I have two buttons that adds/deletes a row in a datagrid
<Button Name="BtnAddPoint" Content="Add" Width="70" Margin="10 0 10 0" Click="BtnAddSchedule_Click"></Button>
<Button Name="BtnDeletePoint" Content="Delete" Width="70" Click="BtnDeleteSchedule_Click"></Button>
I have two questions.
Currently, the above trigger disables both of the button when I only want it to disable the delete button. Setting the targetname of the setter to the delete button doesn't work. Can I make the trigger target a particular button?
Also, I'd like the delete button to be only enabled when a grid item is selected rather than checking for the item count. Is this possible?
You should use Style with key if you want to assign it to appropriate button:
<Style x:Key="DeleteButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Button.IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ScheduleDataGrid, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
In BtnDeletePoint button you should add style:
<Button Name="BtnAddPoint" Content="Add" Width="70" Margin="10 0 10 0" Click="BtnAddSchedule_Click"></Button>
<Button Name="BtnDeletePoint" Content="Delete" Width="70" Click="BtnDeleteSchedule_Click" Style="{StaticResource DeleteButtonStyle}"></Button>
Instead of checking HasItems and writing another trigger to check if grid has selected item, you can write trigger and check if SelectedItem is null. SelectedItem property gives you information if grid has items and if user selected one of them.
If both Buttons share the same Style and therefore have the same Trigger, then they are both going to be disabled under the same circumstances (in this case, when the DataGrid has 0 items).
In order to disable the Delete Button under different circumstances you will need to create a separate Style with a different Trigger and apply that style to BtnDeletePoint. Since I don't see you setting the Style in the declaration of the buttons, I would guess the trigger belongs to an Implicit Style for Button, so you'll need to assign an x:Key to the new style so that you can assign it to your delete button:
<Style x:Key="DeleteButtonStyle" TargetType="Button">
<!-- Setters -->
<!-- Triggers -->
</Style>
<Button Name="BtnDeletePoint" Style="{DynamicResource DeleteButtonStyle}" Content="Delete" Width="70" Click="BtnDeleteSchedule_Click"/>
As for the trigger to enable the delete button when only a single item is selected, if you don't want to use the Count of the SelectedItems property on the DataGrid, then you would need to use a Converter to determine the selection state of the grid - there are no other DependencyProperties on the DataGrid that I'm aware of that will provide you that information.
Also, I'm not sure of the context without more of the code, but don't believe you want a DataTrigger in this case - you should be fine with a standard Trigger.