How to find a control in a TabItem - wpf

I want to find a control within a TabItem. Using FindName it is not possible. I found a way traversing the visual tree but that looks rather cumbersome. Why does FindName not do the job?
XAM:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button
Content="Click"
Click="Button_Click"
/>
<TabControl Grid.Row="1" x:Name="tabControl">
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<TextBox
x:Name="textBox"
Text="{Binding DataContext.Message, ElementName=mainWindow}"
/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
<TabItem Header="One" />
<TabItem Header="Two" />
<TabItem Header="Three" />
</TabControl>
</Grid>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
TabControl tabControl = FindName("tabControl") as TabControl;
TextBox textBox = FindName("textBox") as TextBox;
TabItem tabItem = tabControl.SelectedItem as TabItem;
textBox = tabItem.FindName("textBox") as TextBox;
}
}
"textBox" is null regardless whether searched from top or from selected tabItem.

The TextBox is not a visual child of the TabItem. If you look at the visual tree, you'll see that the contents of the currently selected tab is hosted in a ContentPresenter that is part of the TabControl's ControlTemplate.
This should work though:
private void Button_Click(object sender, RoutedEventArgs e)
{
ContentPresenter cp = tabControl.Template.FindName("PART_SelectedContentHost", tabControl) as ContentPresenter;
TextBox textBox = cp.ContentTemplate.FindName("textBox", cp) as TextBox;
}

Related

WPF custom control not working as expected in Listview

I want to put an editabletextblock cutom control in a Listview datatemplate. I'm following this article and it works well.
But when i'm put this control in a Listview datatemplate, on double click on the Textblock, the event OnMouseDoubleClick of the custom control is fired but the Textbox is never display.
My Datatemplate :
<DataTemplate x:Key="ItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal"
Grid.Column="0">
<Image Source="{Binding Icon}"
Margin="0 0 4 0" />
<localp:EditableTextBlock Text="{Binding Tag, Mode=TwoWay}"
VerticalAlignment="Center" />
</StackPanel>
</Grid>
<ListView
ItemTemplate={StaticResource ItemTemplate}
.... />
And i don't know why the OnMouseDoubleClick EditableTextBlock is fired but the inner Textbox is never displayed as expected.
Thanks is advance for your help,
Regards
Change the default values of TextBlockForegroundColorProperty and TextBoxForegroundColorProperty from null to something else:
public static readonly DependencyProperty TextBlockForegroundColorProperty =
DependencyProperty.Register("TextBlockForegroundColor",
typeof(Brush), typeof(EditableTextBlock), new UIPropertyMetadata(Brushes.Black));
public static readonly DependencyProperty TextBoxForegroundColorProperty =
DependencyProperty.Register("TextBoxForegroundColor",
typeof(Brush), typeof(EditableTextBlock), new UIPropertyMetadata(Brushes.Black));
Or set them in you Xaml:
<local:EditableTextBlock TextBlockForegroundColor="Black" TextBoxForegroundColor="Black" ... />
Edit
you can set keyboard focus to the TextBox, however, you should set e.Handled to true, or the OnTextBoxLostFocus will execute and hides your TextBox.
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
this.m_TextBlockDisplayText.Visibility = Visibility.Hidden;
this.m_TextBoxEditText.Visibility = Visibility.Visible;
if (m_TextBoxEditText.IsKeyboardFocusWithin ==false)
{
Keyboard.Focus(m_TextBoxEditText);
e.Handled = true;
m_TextBoxEditText.CaretIndex = m_TextBoxEditText.Text.Length;
}
}

Set focus to 1st textbox in items control

Let's say I have an items control that is bound to a list of items on the VM. Inside the datatemplate is a textbox. How would I set focus to the first textbox in either XAML or the VM?
Thanks in advance for any help!
<ItemsControl ItemsSource="{Binding UsageItems}" Grid.Row="1" Focusable="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Month}" Style="{StaticResource Local_MonthLabel}" />
<core:NumericTextBox Value="{Binding Actual, Mode=OneWay}" Style="{StaticResource Local_ActualUsageEntry}" Grid.Column="2"/>
<core:ValidationControl Instance="{Binding Model}" Grid.Column="4" PropertyName="{Binding MonthNumber, StringFormat=AdjustedUsage{0}}">
<core:NumericTextBox Value="{Binding Adjusted}" DefaultValueIfNull="0" Style="{StaticResource Local_AdjustUsageEntry}" x:Name="AdjustmentEntry" inventoryLocationSetup:InitialFocusBehavior.Focus="True" />
</core:ValidationControl>
<telerik:RadComboBox ItemsSource="{Binding Converter={StaticResource Converter_EnumToEnumMemberViewModel}, Mode=OneTime, Source={x:Type Enums:UsageAdjustmentTypes}}" SelectedValue="{Binding Code, Mode=TwoWay}" Grid.Column="6" Style="{StaticResource Local_CodeSelector}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
I use an attached behaviour:
public static class InitialFocusBehavior
{
public static bool GetFocus(DependencyObject element)
{
return (bool)element.GetValue(FocusProperty);
}
public static void SetFocus(DependencyObject element, bool value)
{
element.SetValue(FocusProperty, value);
}
public static readonly DependencyProperty FocusProperty =
DependencyProperty.RegisterAttached(
"Focus",
typeof(bool),
typeof(InitialFocusBehavior),
new UIPropertyMetadata(false, OnElementFocused));
static void OnElementFocused(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = depObj as FrameworkElement;
if (element == null)
return;
element.Focus();
}
}
Then in the XAML bind it to True for the element you want focused:
<TextBox Width="200" Height="20" local:InitialFocusBehavior.Focus="True" />
=== UPDATE ===
Sorry, the code above just shows how to use a behaviour to give focus to a control on the page, if you want to do it to an element in the first item in an ItemControl then you'll have to instead apply the behavior to the ItemsControl itself and then in your update handler find the child by doing something like this instead:
static void OnElementFocused(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = depObj as ItemsControl;
if (itemsControl == null)
return;
itemsControl.Loaded += (object sender, RoutedEventArgs args) =>
{
// get the content presented for the first listbox element
var contentPresenter = (ContentPresenter)itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
// get the textbox and give it focus
var textbox = contentPresenter.ContentTemplate.FindName("myTextBox", contentPresenter) as TextBox;
textbox.Focus();
};
}
You'll notice that I'm setting the focus inside the OnLoaded handler because I'm assuming that the items won't have been attached yet when the control is first created.
Also you've probably already figured it out by "local" is just the namespace that the InitialFocusBehavior class is defined in, you'll need to add something like this at the top of the xaml:
xmlns:local="clr-namespace:YourProjectNamespace"

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>

Silverlight 4: how to show ToolTip on keyboard focus (revised)

My original question:
Is there an easy way for a ToolTip to be shown when an item gets keyboard focus, not just mouse over? We have a list of items with tooltips that users will probably tab through, and the desired behavior is for a tooltip to be shown then too.
Added example XAML. A HyperlinkButton with the Tooltip set is what needs the keyboard focus as well.
<DataTemplate x:Key="OfferingItemDT">
<HyperlinkButton Command="{Binding Path=NavigateToLinkCommand}" ToolTipService.ToolTip="{Binding Tooltip}">
<Grid x:Name="gOfferingButtonRoot" Width="275" MaxHeight="78" Margin="5,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image x:Name="imgServiceOfferingIcon"
Grid.RowSpan="2"
VerticalAlignment="Top"
Source="{Binding Path=Image, Converter={StaticResource ByteArrayToImageConverter}}"
Stretch="UniformToFill"
Margin="2,10,0,0"
MaxHeight="32" MaxWidth="32"
/>
<TextBlock x:Name="txbOfferingTitle"
Grid.Column="1"
Grid.Row="0"
Text="{Binding Title}"
TextWrapping="Wrap"
Style="{StaticResource OfferingTileTitleText}"/>
<TextBlock x:Name="txbOfferingDesc"
Grid.Column="1"
Grid.Row="1"
Style="{StaticResource OfferingTileBodyText}"
Text="{Binding BriefDescription}" />
</Grid>
</HyperlinkButton>
</DataTemplate>
Updated:
Based on info in WPF: Show and persist ToolTip for a Textbox based on the cursor as well as Anthony's comments, I tried this code in the GotFocus eventhandler:
private void showTooltip(object sender, RoutedEventArgs e)
{
HyperlinkButton hb = new HyperlinkButton();
ToolTip ttip = new ToolTip();
hb = sender as HyperlinkButton;
ttip = ToolTipService.GetToolTip(hb) as ToolTip;
ttip.IsOpen = true;
}
This seems like it would work, but ttip is always null. Help?
"Easy" is subjective term. Yes its easy. On the same UI element on which you attach the ToolTip you can hook the GotFocus and LostFocus event handler the will use ToolTipService.GetToolTip to acquire the tooltip and the set IsOpen to true and false respectively.
The missing part is to define the tooltip in XAML so that we can access the Tooltip element.
<HyperlinkButton MouseLeftButtonUp="showTooltip">
<ToolTipService.ToolTip>
<ToolTip>
<TextBlock Text="My tooltip text"/>
</ToolTip>
</ToolTipService.ToolTip>
<!-- ... -->
</HyperlinkButton>
Code behind
private void showTooltip(object sender, RoutedEventArgs e)
{
FrameworkElement frameworkElement = (FrameworkElement)sender;
ToolTip tooltip = ToolTipService.GetToolTip(frameworkElement) as ToolTip;
if (tooltip != null)
{
tooltip.IsOpen = true;
frameworkElement.MouseLeave += new MouseEventHandler(frameworkElement_MouseLeave);
}
}
static void frameworkElement_MouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement frameworkElement = (FrameworkElement)sender;
frameworkElement.MouseLeave -= new MouseEventHandler(frameworkElement_MouseLeave);
ToolTip tooltip = ToolTipService.GetToolTip(frameworkElement) as ToolTip;
if (tooltip != null)
{
tooltip.IsOpen = false;
}
}

Access control within datatemplate

This is XAML:
<Window.Resources>
<DataTemplate x:Key="Temp">
<DockPanel Width="Auto" Background="White" LastChildFill="False">
<TextBox Name="txtBox" TextWrapping="Wrap" DockPanel.Dock="Left" Text="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Content}" Height="20" Width="100"/>
<StackPanel Orientation="Vertical">
<RadioButton Content="Option1" HorizontalAlignment="Left" Height="16" Width="112" Click="RadioButton_Click" />
</StackPanel>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl ContentTemplate="{DynamicResource Temp}" Content="1"/>
</Grid>
This is codebehind:
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
StackPanel sp = ((RadioButton)sender).Parent as StackPanel;
DockPanel dp = sp.Parent as DockPanel;
TextBox txtbox = dp.FindName("txtBox") as TextBox;
MessageBox.Show(txtbox.Text);
}
Is there a more simple way to access the textbox?
(As I know I can't get Parent of parent e.g. Parent.Parent...)
Your code is not that complex!
However, you could simplify it by using Linq-to-VisualTree:
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
RadioButton rb = sender as RadioButton;
TextBox txtbox= rb.Ancestors<DockPanel>().First().Elements<TextBox>().First() as TextBox;
MessageBox.Show(txtbox.Text);
}
The Linq query above finds the first DockPanel ancestor of your RadioButton (i.e. the Parent.Parent that you wanted!), then finds the first TextBox child of the DockPanel.
However, I typically use Linq-to-VisualTree in cases where the query is more complex. I thin your approach is OK really!
Among other things you can add a reference to it in the RadioButton.Tag:
<RadioButton Content="Option1" HorizontalAlignment="Left" Height="16" Width="112"
Click="RadioButton_Click" Tag="{x:Reference txtBox}" />
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
var textBox = (sender as FrameworkElement).Tag as TextBox;
//...
}

Resources