Focus DataTemplate in WPF - wpf

The behaviour I am looking for is that a selection of a Item in a ListView results in focusing the first focusable visualchild.
Problem: datatemplated data in a ItemsControler which does not get an initial focus.
In the example below there are 4 Strings which are then stuffed into a TextBox via Datatemplate.
Example:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type sys:String}" >
<TextBox Width="100" Text="{Binding Mode=OneWay}"/>
</DataTemplate>
</ListView.Resources>
<ListView.ItemsSource>
<x:Array Type="{x:Type sys:String}">
<sys:String>test</sys:String>
<sys:String>test</sys:String>
<sys:String>test</sys:String>
<sys:String>test</sys:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</Window>
I already tried some combinations of
FocusManager.FocusedElement="{Binding ElementName=[...]}"
pointless to say: without success.
Anyone a clou how I could get what I want without traversing the visual tree in c#?
It should be possible to do this, shouldn't it?

The FocusManager works sweet for this.
<DataTemplate x:Key="MyDataTemplate" DataType="ListBoxItem">
<Grid>
<WrapPanel Orientation="Horizontal" FocusManager.FocusedElement="{Binding ElementName=tbText}">
<CheckBox IsChecked="{Binding Path=Completed}" Margin="5" />
<Button Style="{StaticResource ResourceKey=DeleteButtonTemplate}" Margin="5" Click="btnDeleteItem_Click" />
<TextBox Name="tbText"
Text="{Binding Path=Text}"
Width="200"
TextWrapping="Wrap"
AcceptsReturn="True"
Margin="5"
Focusable="True"/>
<DatePicker Text="{Binding Path=Date}" Margin="5"/>
</WrapPanel>
</Grid>
</DataTemplate>

Using the binding syntax in C# will not work, because that is how bindings are described in XAML. It might work to create a new instance of System.Windows.Data.Binding, and set its properties to the equivalent of what you would set in XAML, but I'm not sure what ElementName that you would be able to bind to in order to get this working correctly.
The only other option that I can think of is to add a handler for the SelectionChanged event, and set the focus manually, but that doesn't sound like the solution that you want.
Upon further inspection, I don't think that a Binding solution is possible. FocusManager.FocusedElement is an IInputElement, which doesn't have any members related to binding. I think that you need to traverse the visual tree in order to find the first object that is focusable. Something like this should work:
// Event handler for the ListBox.SelectionChanged event
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
ItemContainerGenerator = generator.listBox.ItemContainerGenerator;
ListBoxItem selectedItem =
(ListBoxItem)generator.ContainerFromIndex(listBox.SelectedIndex);
IInputElement firstFocusable = FindFirstFocusableElement(selectedItem);
firstFocusable.Focus();
}
private IInputElement FindFirstFocusableElement(DependencyObject obj)
{
IInputElement firstFocusable = null;
int count = VisualTreeHelper.GetChildrenCount(obj);
for(int i = 0; i < count && null == firstFocusable; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
IInputElement inputElement = child as IInputElement;
if(null != inputElement && inputElement.Focusable)
{
firstFocusable = inputElement;
}
else
{
firstFocusable = FindFirstFocusableElement(child);
}
}
return firstFocusable;
}

Related

Binding to a viewmodel property in a DataTemplate

I'm fairly new to XAML but enjoying learning it. The thing I'm really struggling with is binding a property to an element in a DataTemplate.
I have created a simple WPF example to, (hopefully,) explain my problem.
I this example I am trying to bind the Visibility property of a CheckBox in a DataTemplate to a Property in my viewmodel. (Using this scenario purely for learning/demo.)
I have a simple DataModel named Item, but is of little relevance in this example.
class Item : INotifyPropertyChanged
{
// Fields...
private bool _IsRequired;
private string _ItemName;
And a fairly simple View Model named ItemViewModel.
class ItemViewModel : INotifyPropertyChanged
{
private ObservableCollection<Item> _Items;
private bool _IsCheckBoxChecked;
private bool _IsCheckBoxVisible;
public ObservableCollection<Item> Items
{
get { return _Items; }
set { _Items = value; }
}
public bool IsCheckBoxChecked
{
get { return _IsCheckBoxChecked; }
set
{
if (_IsCheckBoxChecked == value)
return;
_IsCheckBoxChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxChecked"));
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
}
}
public bool IsCheckBoxVisible
{
get { return !_IsCheckBoxChecked; }
set
{
if (_IsCheckBoxVisible == value)
return;
_IsCheckBoxVisible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
(Constructors and INotifyPropertyChanged implementation omitted for brevity.)
Controls laid out in MainPage.xaml as follows.
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ItemViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox x:Name="checkBox" Content="Hide CheckBoxes" FontSize="14" IsChecked="{Binding IsCheckBoxChecked, Mode=TwoWay}" />
<ListView ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" >
<ListView.ItemTemplate >
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemName}"/>
<CheckBox Grid.Column="1" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" >
<CheckBox.DataContext>
<local:ItemViewModel/>
</CheckBox.DataContext>
</CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Margin="4,4,0,0">
<TextBlock Text="IsCheckBoxVisible:"/>
<TextBlock Text="{Binding IsCheckBoxVisible}" Margin="4,0,0,0" FontWeight="Bold" />
</StackPanel >
<Button Content="Button" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" Margin="4,4,4,4"/>
</StackPanel>
</Grid>
The 'Hide CheckBoxes' checkbox is bound to IsCheckBoxChecked and is used to update IsCheckBoxVisible. I've also added a couple of extra controls below the DataTemplate to prove, (to myself,) the everything works.)
I have also implemented Jeff Wilcox's value converter. (Thank you.) http://www.jeff.wilcox.name/2008/07/visibility-type-converter/
When I run the app, checking and unchecking the 'Hide Checkbox', controls outside the DataTemplate function as expected but, alas, the Checkbox inside the data template remains unchanged.
I have had success with:
IsVisible="{Binding IsChecked, Converter={StaticResource VisibilityConverter}, ElementName=checkBox}"
But I'm not just trying mimic another control but make decisions based on a value.
I would REALLY appreciate any help or advice you can offer.
Thank you.
When you are in a DataTemplate, your DataContext is the data templated object, in this case an Item. Thus, the DataContext of the CheckBox in the DataTemplate is an Item, not your ItemViewModel. You can see this by your <TextBlock Text="{Binding ItemName}"/>, which binds to a property on the Item class. The Binding to IsCheckBoxVisible is trying to find a property called IsCheckBoxVisible on Item.
There are a couple of ways around this, but by far the easiest is to do this:
On your Window (in the xaml), give it and x:Name. Eg:
<Window [...blah blah...]
x:Name="MyWindow">
Change your binding to look like this:
<CheckBox Grid.Column="1"
Visibility="{Binding DataContext.IsCheckBoxVisible, ElementName=MyWindow, Converter={StaticResource VisibilityConverter}}">
We're using the Window as the source for the Binding, then looking at its DataContext property (which should be your ItemViewModel, and then pulling off the IsCheckBoxVisible property.
Another option, if you want something fancier, is to use a proxy object to reference your DataContext. See this article on DataContextProxy.

Using FindAncestor from within an Itemscontrol datatemplate to find a textblock outside of the datatemplate

I have a ItemsControl that's bound to an object, within the datatemplate of the ItemsControl i have two textblocks, I want to bind the first textblock's text property to another textblock that sits outside this ItemsControl.
I have tried finding the object in the parent datacontext and also simply trying to find the TextBlock with the Path=Text
one example is below :
<TextBlock Name="Name" Text="{Binding Name}"
Grid.Column="0"
FontSize="{DynamicResource SmallSize}"
TextWrapping="Wrap"
TextAlignment="Right"
Padding="4,0,0,0"
Grid.ColumnSpan="2" Background="Aqua"/>
<ItemsControl ItemsSource="{Binding TheValue}"
Padding="4,0,0,0"
Grid.Column="2"
HorizontalAlignment="Right">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text = "{
Binding RelativeSource =
{RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=Name}"
Grid.Column="0"
FontSize="{DynamicResource SmallSize}"
TextWrapping="Wrap" ........................
{Binding RelativeSource = {RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=Name}
Here you say to WPF find first parent of this control with Type Window, e.g. it's "ParentWindow". After this binding occurs to "ParentWindow" Name property.
If you want enable binding to control, which defined in same XAML, you can set the source explicitly by using Binding.ElementName property.
This is example for you code:
<TextBlock Text = "{Binding ElementName=Name, Path=Text}"/>
By the way, using control name as "Name" not is good. If you use this control form code behind it's looking as Name.Text = "some text", which can cause a trouble to understand what is going on.
UPDATE:
Example of binding to control DataContext Property in different datatemplate
class MainViewModel
{
public Class1 C1 { get; set; }
public Class2 C2 { get; set; }
public MainViewModel()
{
C1 = new Class1 { S1 = "This is C1 data context" };
C2 = new Class2 { S2 = "This is C2 data context" };
}
}
In XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MainViewModel}">
<StackPanel>
<ContentControl Name="cc1" Content="{Binding C1}"/>
<ContentControl Name="cc2" Content="{Binding C2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class1}">
<TextBlock Text="{Binding S1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class2}">
<TextBlock Text="{Binding ElementName=cc1, Path=DataContext.C1.S1}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
</Window>
But, I don't think something like this is a good approach. Especially, because this can be many items with this DataTemplate. Maybe you need delegate this to your ViewModel.

Show and focus TextBox in DataTemplate

I have searched high and low, but I can't figure this one out. I am building a ListBox that has editable items. I have a DataTemplate for the ListBox.ItemTemplate that contains (among other things) a TextBlock and a TextBox. The TextBlock is always visible, and the TextBox is only visible after the user double-clicks on the TextBlock. When the user clicks another item in the list, the TextBox hides again to show the TextBlock. All of this works great. See my code:
XAML
<Window.Resources>
<local:GoalCollection x:Key="goals"/>
<DataTemplate x:Key="GoalItemTemplate" DataType="local:Goal">
<Grid>
<TextBlock Text="{Binding Title}"
MouseLeftButtonDown="TextBlock_MouseLeftButtonDown"
VerticalAlignment="Center"/>
<TextBox Name="EntryBox"
Text="{Binding Title}"
Visibility="Hidden"
BorderBrush="{x:Null}"
Padding="-2,0,0,0"
Panel.ZIndex="1"
Margin="-2,0,0,0"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<ListBox Name="GoalsList"
ItemsSource="{Binding Source={StaticResource goals}}"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource GoalItemTemplate}"
SelectionChanged="GoalsList_SelectionChanged" />
</Grid>
C#
public partial class MainWindow : Window
{
GoalCollection goals;
public MainWindow()
{
InitializeComponent();
}
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject { ... }
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
goals = (GoalCollection)Resources["goals"];
}
private void TextBlock_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
TextBlock tblk = sender as TextBlock;
if (tblk == null)
return;
TextBox tbx = ((Grid)tblk.Parent).FindName("EntryBox") as TextBox;
if (tbx == null)
return;
tbx.Visibility = Visibility.Visible;
Keyboard.Focus(tbx);
}
}
private void GoalsList_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListBoxItem lbi;
ContentPresenter cp;
DataTemplate dt;
TextBox tbx;
foreach (Goal item in e.RemovedItems)
{
lbi = (ListBoxItem)GoalsList.ItemContainerGenerator.
ContainerFromItem(item);
cp = FindVisualChild<ContentPresenter>(lbi);
dt = cp.ContentTemplate;
tbx = (TextBox)dt.FindName("EntryBox", cp);
if (tbx == null)
continue;
tbx.Visibility = Visibility.Hidden;
}
}
}
The problem that I'm having is that the TextBox immediately shifts focus back to the host ListBoxItem after the double-click. An additional (third) click is required to focus on the TextBox.
Tracing through this, I have found that the TextBox does indeed receive focus. But then it immediately loses it (try adding a handler for the TextBox.LostKeyboardFocus event and step through and out of the `TextBlock_MouseLeftButtonDown()' method). Any ideas?
Thanks.
My guess is that the click event is bubbling up to the ListBox and it's handling it by selecting the item.
Try adding this to your Click event handler (after Keyboard.Focus(tbx);)
e.Handled = true;
If you want to give focus to a child element, try the FocusManager.
<DataTemplate x:Key="MyDataTemplate" DataType="ListBoxItem">
<Grid>
<WrapPanel Orientation="Horizontal"
FocusManager.FocusedElement="{Binding ElementName=tbText}">
<CheckBox IsChecked="{Binding Path=Completed}" Margin="5" />
<Button Style="{StaticResource ResourceKey=DeleteButtonTemplate}"
Margin="5" Click="btnDeleteItem_Click" />
<TextBox Name="tbText"
Text="{Binding Path=Text}"
Width="200"
TextWrapping="Wrap"
AcceptsReturn="True"
Margin="5"
Focusable="True"/>
<DatePicker Text="{Binding Path=Date}" Margin="5"/>
</WrapPanel>
</Grid>
</DataTemplate>

WPF: weird problem in dataBinding with TabControl

I'm trying to use DataBinding for dynamically populating a TabControl but have a problem. dataBinding runs fine but I would like the content of each TabItem to be independent one from the other. Here is my XAML code:
<TabControl
DockPanel.Dock="Left"
ItemsSource="{Binding OpenChats}"
Name="tabChats"
VerticalAlignment="Top"
Width="571">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
TabItems are created with different headers (as I want) but when the user types something in the TextBox inside the ContentTemplate, the same text is maintained in different tabItems and I don't want this.
What am I doing wrong?
I had same problem. This answer helped me. My solution was to remove focus from textbox when tab changed. When focus from textbox is removed, new content is set to binded property as expected.
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DependencyObject focusedElement = (FocusManager.GetFocusedElement(tabControl) as DependencyObject);
if (focusedElement != null)
{
DependencyObject ancestor = VisualTreeHelper.GetParent(focusedElement);
while (ancestor != null)
{
var element = ancestor as UIElement;
if (element != null && element.Focusable)
{
element.Focus();
break;
}
ancestor = VisualTreeHelper.GetParent(ancestor);
}
}
}
or use
Text="{Binding UpdateSourceTrigger=PropertyChanged}"
on textbox binding.
The TextBox in the ContentTemplate has no Binding. Try
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
Adjust the bindingpath if necessary

WPF Scroll & focus changing problem

I am having a problem with scrolling for my WPF application.
Here is the deal. My UI is the following:
The role of my application is to act as a central hub for many applications and launch them. An admin can launch a dump recorded by another user.
Therefore, I have a ListView, showing the application list, which is scrollable if needed.
I defined a GroupStyle in order to show expanders and emulate a Windows Explorer view.
Everything works fine, I just have a problem: when scrolling with the mouse wheel, the component in clear blue ("Launch mode") seems to be catching the focus and stop scrolling.
This means especially that if my mouse is anywhere out of this control, the scrolling is okay. But whenever the mouse enters this control, I can't scroll anymore.
I tried to modify the property Focusable and set it to False everywhere I could but nothing changed. I'd guess that it is finally not a focus problem.
Anybody has an idea on how to avoid the scrolling to be caught by the element?
Here is some (simplified, removed some useless properties so as to make it as clear as possible) XAML for the expander's content:
<StackPanel Orientation="Vertical" VerticalAlignment="Top" >
<ToggleButton>
<!-- ToggleButton Content... -->
</ToggleButton>
<!-- This is the custom component in which you can see "Launch mode" -->
<my:UcReleaseChooser >
<!-- Properties there. I tried to set Focusable to False, no impact... -->
</my:UcReleaseChooser>
</StackPanel>
And the code for UcReleaseChooser:
<StackPanel HorizontalAlignment="Stretch"
Focusable="False" ScrollViewer.CanContentScroll="False">
<ListBox ItemsSource="{Binding ListChosenReleases}" BorderBrush="LightGray" Background="AliceBlue"
HorizontalAlignment="Stretch" Focusable="False" ScrollViewer.CanContentScroll="False">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Focusable="False" ScrollViewer.CanContentScroll="False"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch"
Focusable="False" ScrollViewer.CanContentScroll="False">
<TextBlock DockPanel.Dock="Top"
HorizontalAlignment="Left" Text="{Binding Key}"
FontStyle="Italic"/>
<ListBox DockPanel.Dock="Bottom"
HorizontalAlignment="Right" ItemsSource="{Binding Value}"
BorderBrush="{x:Null}" Background="AliceBlue"
Focusable="False" ScrollViewer.CanContentScroll="False">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Focusable="False"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<-- Blah blah about style -->
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Key}" Margin="3"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBoxItem}}}"
Focusable="False" ScrollViewer.CanContentScroll="False"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
As you can see, the UcReleaseChooser contains a list of RadioButton lists. I tried to set Focusable & CanContentScroll to False everywhere it seemed appropriate, but the control keeps preventing the main UI to scroll...
I guess I should change another property... Any idea?
Thanks!
The problem is the ListBox, or more specifically, the ScrollViewer within the ListBox's template. This is getting your scroll events and consuming them before the outer ScrollViewer in the ListView even sees them.
I would advise replacing the ListBox with an ItemsControl if possible. However, that implies there will be no SelectedItem property. If you need that, I would suggest setting ScrollViewer.HorizontalScrollBarVisibility (or VerticalScrollBarVisibility) to Disabled. Failing that, I can only suggest re-templating ListBox to not contain a ScrollViewer at all.
I had an issue with a listbox stealing focus inside a scrollviewer (I have multiple listboxes inside the scrollviewer). So I created an attached property which denies the listbox the ability to scroll. So therefore the scrollviewer which is housing the listbox can scroll
Your control is a listbox, so this should work as is, but there is no reason the Extension should be limited to a Listbox; it just is to match my exact purpose.
public static class ListboxExtensions
{
public static DependencyProperty IgnoreScrollProperty = DependencyProperty.RegisterAttached("IgnoreScroll", typeof(bool), typeof(ListboxExtensions), new UIPropertyMetadata(false, IgnoreScrollChanged));
public static bool GetIgnoreScroll(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(IgnoreScrollProperty);
}
public static void SetIgnoreScroll(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(IgnoreScrollProperty, value);
}
private static void IgnoreScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newValue = (bool)e.NewValue;
var oldValue = (bool)e.OldValue;
var frameworkElement = d as FrameworkElement;
if (frameworkElement == null) return;
if (!newValue || oldValue || frameworkElement.IsFocused) return;
var lb = frameworkElement as ListBox;
if (lb == null) return;
lb.PreviewMouseWheel += LbOnPreviewMouseWheel;
}
private static void LbOnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!(sender is ListBox) || e.Handled) return;
e.Handled = true;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = ((Control)sender).Parent as UIElement;
if (parent != null) parent.RaiseEvent(eventArg);
}
}
And then in your XAML, you just put this on the listbox:
<ListBox extensions:ListboxExtensions.IgnoreScroll="True">
Of course, remembering to include the namespace to your extensions in the top of the XAML:
xmlns:extensions="clr-namespace:UI.Extensions"

Resources