Need to keep focus in a wpf content panel - wpf

I am switching content template of a ListViewItem at run mode to enable editting the item. For that I am showing a Panel with Ok and Cancel options and I need the user to select any of those option before moving to anotheritem. I want that Panel to behave like a modal Dialog.
Any suggestions?
Advanced Thanks,
Das

You could try listen to PreviewLostKeyboardFocus event and mark it as handled when you don't want to let focus go. Here is an example. We have two columns, and if you put focus into the first column you never go out from it until you click Release Focus button:
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Focus Sample" Height="300" Width="340">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Press Release Focus to leave">
<StackPanel PreviewLostKeyboardFocus="StackPanel_PreviewLostKeyboardFocus">
<TextBox/>
<Button Content="Release Focus"
Click="ReleaseFocusClicked"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Try to switch focus here:"
Grid.Column="1">
<TextBox/>
</GroupBox>
</Grid>
</Window>
C#
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class Window1 : Window
{
private bool _letGo;
public Window1()
{
InitializeComponent();
}
private void StackPanel_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var uie = (UIElement) sender;
var newFocusDO = (DependencyObject)e.NewFocus;
if (!_letGo && !uie.IsAncestorOf(newFocusDO))
{
e.Handled = true;
}
}
private void ReleaseFocusClicked(object sender, RoutedEventArgs e)
{
_letGo = true;
}
}
}
I'm doing one extra check to ensure whether new focus target belongs to our panel. If we don't do this we never let focus leave from the currently focused element. It worth to mention that this approach doesn't keep user from clicking on other buttons in UI. It just holds focus.
Hope this helps.
Cheers, Anvaka.

Related

Close the WPF dialog when one clicks on a button inside the WindowsFormHost

I have a WPF dialog, that hosts a windowsFormHost control, with something like this
<Window x:Class="WPFSort.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFSort"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<WindowsFormsHost HorizontalAlignment="Left" Height="Auto"
Margin="87,43,0,0" VerticalAlignment="Top" Width="Auto">
<local:SimpleWinControl />
</WindowsFormsHost>
</Grid>
</Window>
And for the SimpleWinControl , it is a WinForm control. When button 1 is clicked, I want
The WPF dialog to be closed
And the data importantdata to be "pass out" to the WPF form that calls the WPF dialog?
public partial class SimpleWinControl : UserControl
{
public SimpleWinControl()
{
InitializeComponent();
}
public object importantdata;
private void button1_Click(object sender, EventArgs e)
{
//how should I write the close and pass the importantdata out
}
}
You could for example add a property to your WinForms control that exposes the Button control:
public partial class SimpleWinControl : UserControl
{
public SimpleWinControl()
{
InitializeComponent();
}
public Button TheButton { get { return button1; } }
...
}
Give the WinForms control an x:Name in your XAML markup:
<WindowsFormsHost HorizontalAlignment="Left" Height="Auto" Margin="87,43,0,0" VerticalAlignment="Top" Width="Auto">
<local:SimpleWinControl x:Name="winFormsControl" />
</WindowsFormsHost>
...and hook up to the Click event of the Button in the code-behind of your WPF dialog window:
public partial class Dialog : Window
{
public Dialog()
{
InitializeComponent();
winFormsControl.TheButton.Click += (s, e) => this.Close();
}
}
The window that opens the dialog could then access the importantdata field once the ShowDialog method returns:
private void ShowDialog_Click(object sender, RoutedEventArgs e)
{
Dialog d = new Dialog();
d.ShowDialog();
object importantData = d.winFormsControl.importantdata;
}
Another option may be to raise an event from the WinForms control: https://msdn.microsoft.com/en-us/library/5z57dxz2(v=vs.90).aspx

Caliburn.Micro - Setting focus in new window

My application is a topmost Window switching between multiple UserControl views. Its behavior is to close when the user clicks outside the window (more generally when it loses focus), and to show again when the user clicks on the system tray icon.
I'm having trouble getting the window to get focus when it shows up after clicking the tray icon. The problem is that the window shows up without focus and it doesn't hide when the user clicks outside. The user has to first click into the window and then click outside to trigger the Window's Deactivated event.
I can reproduce the problem with the most basic example from the documentation. I'm showing below the most basic representation of the problem I could produce.
I have tried many different things, none of which have shown any different behavior. For example, I tried calling the view's Focus() in the OnViewLoaded handler in the view models and deactivating the viewmodels instead of closing the window in the Close action. I also tried this suggestion on what seemed to be the same problem.
Any hint or help as to how to do this would be greatly appreciated.
[Export(typeof(ShellViewModel))]
public class ShellViewModel : Conductor<object>
{
IWindowManager windowManager;
[ImportingConstructor]
public ShellViewModel(IWindowManager windowManager)
{
this.windowManager = windowManager;
ShowPageOne();
}
public void ShowPageOne()
{
ActivateItem(new PageOneViewModel());
}
public void ShowPageTwo()
{
ActivateItem(new PageTwoViewModel());
}
public void Close()
{
this.TryClose();
// Using this to simulate the user clicking on a system tray icon
var timer = new Timer();
timer.Tick += (s, e) =>
{
windowManager.ShowWindow(this);
timer.Stop();
};
timer.Interval = 1000;
timer.Start();
}
}
My ShellView is:
<Window x:Class="PopupTest.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
Width="300" Height="400"
cal:Message.Attach="[Event Deactivated] = [Action Close]"
Topmost="True">
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="ShowPageOne" Content="Show Page One" />
<Button x:Name="ShowPageTwo" Content="Show Page Two" />
</StackPanel>
<ContentControl x:Name="ActiveItem" />
</StackPanel>
My two view models are:
public class PageOneViewModel : Caliburn.Micro.Screen { }
public class PageTwoViewModel : Caliburn.Micro.Screen { }
And the views are:
<UserControl x:Class="PopupTest.PageOneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Name="bob" FontSize="32">Page One</TextBlock>
</UserControl>

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;
}
}

Making UserControls accessible in WPF

I have a WPF form, which consists of a grid of two columns.
In the left-hand column are the control labels, and in the right-hand column are my controls.
The controls are all UserControls. In the simplest case, some of these controls simply wrap existing WPF controls such as the textbox, so that they all implement a common interface.
When the form is generated, I have code like this to set the label for the associated control, where newControl is the created UserControl and ctl.Caption simply returns the required label text:
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newLabel.Target = newControl;
One problem is that setting the Target doesn't actually work. If I have an underscore in the caption, the mnemonic key doesn't set focus to the wrapped control. One workaround for this may be to manually set the focus to the wrapped control within the UserControl code - but...
The biggest problem is accessibility. Screenreaders such as JAWS, and Windows built-in Narrator, do not read the control caption when the control receives focus.
I have had a look at this: http://msdn.microsoft.com/en-us/library/windows/desktop/gg712258.aspx - which provides a lot of detail, but no helpful examples. It has a lot of stuff about custom controls, which is surely overkill for a simple user control?
So, how can I "attach" my labels correctly to my UserControls?
You can browse the code for the entire project at http://quest.codeplex.com/SourceControl/changeset/view/676933506953 - the particular code is in the EditorControls project, and the UserControls are instantiated in ElementEditor.xaml.cs.
Your newControl is of type Control that doesn't allow you to add additional content.
If you want to add some content to it you need to use a class that supports it, like ContentControl or Panel (for multiple childs) you can implement you own control that implements the IAddChild interface.
A simple solution for you problem could be :
<UserControl x:Class="UIMocks.MyCustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="content"/>
</UserControl>
The code-behind
[ContentProperty("Children")]
public partial class MyCustomControl : UserControl
{
public MyCustomControl()
{
InitializeComponent();
}
public UIElementCollection Children { get { return content.Children; } }
}
and then you can use
MyCustomControl newControl = InitialiseEditorControl(ctl);
...
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newControl.Children.Add(newLabel);
hmmm I tried to reproduce your issue on a small test project, but for me it works... so I guess you'll have to give more details on how your userControls are built. Here is what works for me:
I created an Empty project (just the App and Window files, as usual) and set up a grid with 2 columns in my window:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Name="Window"
SizeToContent="WidthAndHeight">
<Grid Name="MyGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
</Grid>
</Window>
then created a userControl that extends the wpf TextBox class:
<TextBox x:Class="Test.MyTextBox"
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">
</TextBox>
and:
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
}
}
it's basically a textbox with a "Caption" dp.
and now in My window's code behind:
public MainWindow()
{
InitializeComponent();
MyTextBox tb = new MyTextBox { Caption = "_Foo", Width = 100 };
Label lb = new Label { Content = tb.Caption + ":", Target = tb };
MyGrid.Children.Add(lb);
MyGrid.Children.Add(tb);
Grid.SetColumn(lb, 0);
Grid.SetColumn(tb, 1);
}
and with this, I do get focus on the TB when I press ALT + F (I can even see the _ under the F of "Foo" in the Label when just pressing ALT)
So I guess your issue has to do with your UserControls themselves and how they are built (what Template for instance)
Edit:
If your control is not extending an existing control but rather contains a WPF control, the issue is probably on the Focus method. You should add a Focus() method that sets the focus on the right part of your control when the control itself gets the focus.
code (for a UserControl containing a textbox that you want to get the focus for instance):
<TextBox x:Class="Test.MyTextBox"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="foo" Grid.Column="0" />
<TextBox Name="TextBoxPart" Grid.Column="1" />
</Grid>
</TextBox>
code behind
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
TextBoxPart.Focus();
}
}
Edit 2:
I had an issue once, to transfer the focus to a subcontrol in a dataGridCell, and here is what I did in the template:
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="TextBoxPart" Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBoxPart}" />
</Trigger>
</ControlTemplate.Triggers>
you could try adding this to your template. This should transfer your focus alright.
as for the accessibility, I don't think this will help, but I do not see any way of achieving what you want :-/

Is faking a WPF mouseover possible?

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.

Resources