How to show tooltip in code behind in WPF - c#

How can I show a tooltip in code-behind? The code below defines my question better. Obviously I don't want the code to check for mouse position etc, just how to display the tooltip.
private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
{
// if mouse position equals certain coordinates show the tooltip
}

Try like this:
if (control.ToolTip != null)
{
// Main condition
if (control.ToolTip is ToolTip)
{
var castToolTip = (ToolTip)control.ToolTip;
castToolTip.IsOpen = true;
}
else
{
toolTip.Content = control.ToolTip;
toolTip.StaysOpen = false;
toolTip.IsOpen = true;
}
}
The Main condition necessary, because ToolTip for Control can be set in two approaches:
First approach
<Button Name="TestButton"
ToolTip="TestToolTip" />
This approach is most common. In this case, the content of the ToolTip will object and not type of ToolTip.
Second approach
<Button Name="TestButton"
Content="Test">
<Button.ToolTip>
<ToolTip>TestToolTip</ToolTip>
</Button.ToolTip>
</Button>
Is the same as this:
<Button Name="TestButton"
Content="Test">
<Button.ToolTip>
TestToolTip
</Button.ToolTip>
</Button>
In this case, the Content type of ToolTip will be Tooltip. Note that in the second case, the object automatically fills ToolTip object on line TestToolTip, hence this approach will work a bit slower.
Therefore, this check is needed to avoid an exception when we try to assign to the ToolTip the content of the ToolTip type here:
toolTip.Content = control.ToolTip;
Below is a full example:
XAML
<Grid>
<Button Name="TestButton"
Width="100"
Height="25"
Content="Test"
ToolTip="TestToolTip" />
<Button Name="ShowToolTip"
VerticalAlignment="Top"
Content="ShowToolTip"
Click="ShowToolTip_Click" />
</Grid>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowToolTip_Click(object sender, RoutedEventArgs e)
{
var toolTip = new ToolTip();
if (TestButton.ToolTip != null)
{
if (TestButton.ToolTip is ToolTip)
{
var castToolTip = (ToolTip)TestButton.ToolTip;
castToolTip.IsOpen = true;
}
else
{
toolTip.Content = TestButton.ToolTip;
toolTip.StaysOpen = false;
toolTip.IsOpen = true;
}
}
}
}

Related

Get TextBlock inside Button

I have a Button with a TextBlock embedded inside. When the Button is clicked, I want to be able to fetch the TextBlock inside it and modify it's members.
Here is how my button is setup:
<Button Click="Select_Click" Style="{StaticResource ButtonStyle}" HorizontalAlignment="Left" Padding="0,20,20,20">
<TextBlock Text="My text" FontSize="20" Style="{StaticResource TextBlockStyle}"/>
</Button>
In my code behind I want to be able to access the embedded TextBlock:
public void Select_Click(object sender, RoutedEventArgs e)
{
// Get the `TextBlock` from `sender` here
}
I've taken a look at the visual tree of the Button but I'm not seeing the TextBlock. I called GetVisualChildren() on the Button but I only see a Grid and no way to get to the Textblock.
The content of the Button is stored in its Content property and in your case, the TextBlock is the content of the Button.
public void Select_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
TextBlock textBlock = (TextBlock)button.Content;
}
Just do some casting and it's pretty simple
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Establish_handlers();
}
void Establish_handlers()
{
Mybutton.Click += Mybutton_Click;
}
private void Mybutton_Click(object sender, RoutedEventArgs e)
{
Button clicked_button = (Button)sender;
TextBlock desired_text = (TextBlock)clicked_button.Content;
Textbox_Show_Button_Content.Text = desired_text.Text;
}
}
<StackPanel>
<Button x:Name="Mybutton">
<TextBlock>Hello</TextBlock>
</Button>
<TextBox x:Name="Textbox_Show_Button_Content"></TextBox>
</StackPanel>

change textcolor after textbox loses focus

I would like to change the textbox default textcolor to the original default color after item is added to a list.
XAML
<TextBox Name="AddLocationTextBox" Text="{Binding Path=AddLocationName, UpdateSourceTrigger=PropertyChanged}"
LostFocus="AddLocationTextBox_LostFocus" GotFocus="AddLocationTextBox_GotFocus" HorizontalAlignment="Left" Height="23" Margin="10,37,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="285">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddLocationCommand}" />
</TextBox.InputBindings>
</TextBox>
Code behind in View
public LocationManagerView()
{
InitializeComponent();
AddLocationTextBox.Foreground = Brushes.Gray;
}
private void AddLocationTextBox_GotFocus(object sender, RoutedEventArgs e)
{
AddLocationTextBox.Text = string.Empty;
AddLocationTextBox.Foreground = Brushes.Black;
}
private void AddLocationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
AddLocationTextBox.Foreground = Brushes.Gray;
}
ViewModel
public RelayCommand AddLocationCommand { get; private set; }
private void AddLocation()
{
if ( AddLocationName != null)
{
Locations.Add(new Location()
{
Name = AddLocationName,
});
AddLocationName = "Enter New Location";
Keyboard.ClearFocus();
////change textcolor to gray////
}
else
{
return;
}
}
It's after AddLocationName is set again to "Enter New Location" I want to change the textcolor back to gray.
Looks like the Keyboard.ClearFocus() doesn't call the LostFocus method in the View.
Any Idea how I make this work?
If you just want to change the foreground of your TextBox to gray, instead of tying to force AddLocationTextBox to lose it's focus, you should change the foreground manually (i.e. use AddLocationTextBox.Foreground = Brushes.Gray instead of Keyboard.ClearFocus()).
If you're looking for a way to force currently focused element to lose focus, then you can use below code instead of Keyboard.ClearFocus() :
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));

Soft keyboard overlaps TextBoxes and makes them unreachable

How is it possible to reach an input field within a ScrollViewer when the input field is overlapped by the soft keyboard?
This scenario is easily reproduced:
Create a new page with a ScrollViewer containing some TextBoxes. Make as many TextBoxes as you need until you need to scroll the page to reach the last three TextBoxes.
<ScrollViewer>
<StackPanel Orientation="Vertical">
<TextBox Margin="20" />
<TextBox Margin="20" />
<TextBox Margin="20" />
..
<TextBox Margin="20" />
<TextBox Margin="20" />
<TextBox Margin="20" PlaceholderText="3" />
<TextBox Margin="20" PlaceholderText="2" />
<TextBox Margin="20" PlaceholderText="1" />
</StackPanel>
</ScrollViewer>
Start the app and tap into "Placeholder 3". The keyboard pops up and overlaps "Paceholder 2" and "Placeholder 1".
How can I improve the layout so I can reach these TextBoxes ("1" and "2") without closing and re-opening the keyboard all the time?
An example that shows a working solution can be found on every WindowsPhone: Settings => VPN => Enable VPN => Add new profile => Click in any of the TextBoxes and you'll see that you can scroll to every part of the layout although the soft keyboard is up.
Been awhile on this question but for others who may be looking for a good solution here is what I did.
Subscribe to the keyboard show and hide events and size the height of the scrollviewer based on when the keyboard is showing or hiding.
Xaml
<ScrollViewer x:Name="scrlvwrKBScroll" VerticalScrollMode="Enabled">
<StackPanel Orientation="Vertical">
<TextBox Margin="20" />
<TextBox Margin="20" />
<TextBox Margin="20" />
..
<TextBox Margin="20" />
<TextBox Margin="20" />
<TextBox Margin="20" PlaceholderText="3" />
<TextBox Margin="20" PlaceholderText="2" />
<TextBox Margin="20" PlaceholderText="1" />
</StackPanel>
</ScrollViewer>
C#
public Constructor()
{
this.InitializeComponent()
InputPane.GetForCurrentView().Showing += Keyboard_OnShow;
InputPane.GetForCurrentView().Hiding += Keyboard_OnHide;
}
private void Keyboard_OnShow(InputPane sender, InputPaneVisibilityEventArgs args)
{
this.scrllvwrKBScroll.Height = this.ActualHeight - args.OccludedRect.Height - 50;
}
private void Keyboard_OnHide(InputPane sender, InputPaneVisibilityEventArgs args)
{
this.scrllvwrKBScroll.height = this.ActualHeight;
}
There may be a better way to adjust the height based on the heights of the containers you are using but this is what I used to get my application to work.
I also encountered this problem whenever a Page with a BottomAppBar is displaced in the layout from the root visual. This can be caused by a Margin or Padding on a wrapper element.
Broken visual tree:
Window.Current.Content Frame
Border with 1px Margin
ContentPresenter
Page with BottomAppBar
I could find no "non-disgusting" workaround, but adjusting the offset directly on the root ScrollViewer did work for me. See UWPMobileScrollIssue for a full repro and workaround.
// ...snip...
namespace UWPFocusTestApp
{
sealed partial class App : Application
{
// ...snip...
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// ...snip...
if (rootFrame == null)
{
// ...snip...
// Place the frame in the current Window
Window.Current.Content = rootFrame;
#region WORKAROUND
if (AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
{
InputPane.GetForCurrentView().Showing += InputPane_Showing;
}
#endregion
}
// ...snip...
}
#region WORKAROUND
private void InputPane_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
// we only need to hook once
InputPane.GetForCurrentView().Showing -= InputPane_Showing;
var frame = (Frame)Window.Current.Content;
// Find root ScrollViewer
DependencyObject cNode = frame;
while (true)
{
var parent = VisualTreeHelper.GetParent(cNode);
if (parent == null)
{
break;
}
cNode = parent;
}
var rootScrollViewer = (ScrollViewer)cNode;
// Hook ViewChanged to update scroll offset
bool hasBeenAdjusted = false;
rootScrollViewer.ViewChanged += (_1, svargs) =>
{
// once the scroll is removed, clear flag
if (rootScrollViewer.VerticalOffset == 0)
{
hasBeenAdjusted = false;
return;
}
// if we've already adjusted, bail.
else if (hasBeenAdjusted)
{
return;
}
var appBar = ((Page)frame.Content)?.BottomAppBar;
if (appBar == null)
{
return;
}
hasBeenAdjusted = true;
rootScrollViewer.ChangeView(null, rootScrollViewer.VerticalOffset + appBar.ActualHeight, null);
};
}
#endregion
// ...snip...
}
}

What is the correct way to code the nested methods?

I have created a registration form in silverlight 4, where i have a large number of text-boxes, in front of each text box i have placed a text-block as a required field validator, when any of the textbox left empty while loosing focus, the textblock placed in front of it must become red.
textboxes named textbox1, textbox2 ... and so as the textblocks
the problem is, i do not want code the specific method for each specific textbox, all i want to do is to complete such in just two three methods
here i did some coding which doesn't seems to be correct
private void textBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = (TextBox) sender;
if (textbox.Text == "")
{
var textblock = "textblock" + textBox.Name.Remove(0,7);
TextblockColorChange(textblock);
}
}
private void TextblockColorChange(object sender)
{
var textblock = (TextBlock) sender;
textblock.Foreground= new SolidColorBrush(Colors.Red);
}
please suggest some better way to do so..
I'd create a UserControl that contains the TextBlock and the TextBox and use this UserControl everywhere you currently have the TextBlock and TextBox combination. Then this Usercontrol would have the LostFocus logic inside it and update the TextBlock appropriately. This prevents the need to figure out the right name of the control to update.
you need something like this,
XAML part:
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Horizontal" Height="25">
<TextBox Width="150" LostFocus="TextBox_LostFocus"/>
<TextBlock Text="*" Foreground="#FF0000" VerticalAlignment="Center" Margin="10,0,0,0" Visibility="Collapsed"/>
</StackPanel>
</Grid>
C# Part:
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textbox = sender as TextBox;
if(textbox == null) return;
var stackPanel = textbox.Parent as StackPanel;
if(stackPanel == null) return;
var textBlock = stackPanel.Children.Where(a => a is TextBlock).FirstOrDefault();
if (textBlock == null) return;
if (string.IsNullOrEmpty(textbox.Text)) textBlock.Visibility = Visibility.Visible;
else textBlock.Visibility = Visibility.Collapsed;
}
Whilst I actually prefer Bills approach (although I'd be inclined to use a Templated Control) here is another alternative which is quite fun. In your xaml use this sort of markup:-
<TextBlock Text="Enter Value 1" Foreground="{Binding Tag, ElementName=textBox1, TargetNullValue=Black}" />
<TextBox x:Name="textBox1" LostFocus="txt_LostFocus" />
Your common txt_LostFocus can look like this:-
private void txt_LostFocus(object sender, RoutedEventArgs e)
{
TextBox txt = ((TextBox)sender);
if (String.IsNullOrEmpty(txt.Text))
{
txt.Tag = new SolidColorBrush(Colors.Red);
}
else
{
txt.Tag = null;
}
}
var textblock = "textblock" + textBox.Name.Remove(0,7);
TextblockColorChange(textblock);
This code above will just send a string to TextblockColorChange()
You don't show any other code, but I'm guessing you want to do a FindControl or FindControl like search on that string before passing the result to your code.

How do I know that a Silverlight control has been displayed?

I have a list box displaying the names of help topics which can be added to and the names of the topics changed. Originally it was just displaying strings, but to get the inline editing working I changed it to use a custom type consisting of a string and an InEdit property so the UI can determine whether to display the TextBlock or TextBox:
XAML:
<ListBox ItemsSource="{Binding HelpTopics, Mode=TwoWay}"
SelectedValuePath="Description"
SelectedValue="{Binding SelectedPageId, Mode=TwoWay}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Description, Mode=TwoWay}"
VerticalAlignment="Center"
MouseLeftButtonUp="TopicTextBlock_MouseLeftButtonUp"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=contra}"/>
<TextBox Text="{Binding Description, Mode=TwoWay}"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=pro}"
LostFocus="EditTopicTextBox_LostFocus"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Margin="5" Content="Add Topic" Command="{Binding AddTopicCommand}"/>
HelpTopics is an ObservableCollection<EditableHelpTopic>.
SelectedPageId is a string.
boolToVisibilityConverter is a converter that does what it says.
What works:
Adding a topic creates a new item and adds it to the list and put the item in to edit mode.
Double clicking on an existing item puts that item into edit mode sets the focus to the TextBox and selects all the text so it can be overwritten.
When the TextBox loses focus the edit is saved and the display returns to the TextBlock.
What doesn't work:
When a new topic is added the TextBox should have focus and the text selected so the user can enter a new name.
So my question is is there a point in the code or an event where I know that the TextBox has been created and is visible so I can set focus and select its contents. I've tried hooking into the SelectionChanged event but when that fires the TextBox hasn't yet been displayed. I also added an event to the OnAddTopicExecute method in the view model which I handled in the view, but again that fired before the TextBox was visible.
Below is the code that supports the above XAML. I've tried to cut it down, but there still seems to be a lot of it, so you can skip this if you're not interested ;)
Code behind:
private DateTime lastClickTime = DateTime.MinValue;
private Point lastClickPosition;
private void TopicTextBlock_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if ((DateTime.Now - this.lastClickTime).TotalMilliseconds > 300)
{
this.lastClickPosition = e.GetPosition(element);
this.lastClickTime = DateTime.Now;
}
else
{
Point position = e.GetPosition(element);
if (Math.Abs(this.lastClickPosition.X - position.X) < 4 && Math.Abs(this.lastClickPosition.Y - position.Y) < 4)
{
var textBlock = sender as TextBlock;
var editableHelpTopic = textBlock.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = true;
var parent = textBlock.Parent as Grid;
TextBox textBox = parent.Children.First(c => c.GetType() == typeof(TextBox)) as TextBox;
textBox.Focus();
textBox.SelectAll();
}
}
}
private void EditTopicTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = false;
if (!textBox.Text.Equals(editableHelpTopic.Description))
{
this.editViewModel.RenameTopic(textBox.Text);
}
}
View Model:
public EditViewModel()
{
...
this.AddTopicCommand = new DelegateCommand(this.OnAddTopicExecute, this.OnAddTopicCanExecute);
...
}
where DelegateCommand is an implemetation of ICommand.
private void OnAddTopicExecute(object parameter)
{
var newTopic = new EditableHelpTopic
{
Description = "NewTopic",
InEdit = true
};
this.HelpTopics.Add(newTopic);
this.SelectedPageId = newTopic.Description;
}
Definitions:
public class EditableHelpTopic : INotifyPropertyChanged
{
public bool InEdit { ... }
public string Description { ... }
}
It turned out to be simpler than I thought.
I just needed to add a Loaded event handler to the TextBox:
private void EditTopicTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
if (editableHelpTopic.InEdit)
{
textBox.Focus();
textBox.SelectAll();
}
}

Categories

Resources