What is the least intrusive way to display WPF tooltip on focus? - wpf

What is the minimum number of steps required to display a tooltip when the following control gets focus?
<TextBox ToolTip="Hello there!" ... />
I tried the following in GotFocus
private void ..._GotFocus(object sender, RoutedEventArgs e) {
var element = (FrameworkElement)sender;
var tooltip = element.ToolTip;
if (!(tooltip is ToolTip)) {
tooltip = new ToolTip { Content = tooltip };
element.ToolTip = tooltip;
}
((ToolTip)tooltip).IsOpen = true;
}
However, it seems to ignore the ToolTipService.Placement for this control and SystemParameters.ToolTipPopupAnimationKey set up level higher.
How can I make it work and honor all settings that generally work for tooltips (except the timing, obviously)?

I'd build an IsKeyboardFocused binding in the attached property, like this:
public class ShowOnFocusTooltip : DependencyObject
{
public object GetToolTip(...
public void SetToolTip(...
public static readonly DependencyProperty ToolTipProperty = DependencyProperty.RegisterAttached(..., new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
ToolTipService.SetToolTip(obj,
e.NewValue==null ? null :
BuildToolTip(obj, e.NewValue));
}
});
private object BuildToolTip(DependencyObject control, object content)
{
var tooltip = content is ToolTip ? (ToolTip)content : new ToolTip { Content = content };
tooltip.SetBinding(ToolTip.IsOpenProperty,
new Binding("IsKeyboardFocusWithin") { Source = control });
return tooltip;
}

Don't have a Windows machine to test, but I would have thought:
<TextBox x:Name="textBox">
<TextBox.ToolTip>
<ToolTip IsOpen="{Binding IsKeyboardFocusWithin, ElementName=textBox}">
Whatever
</ToolTip>
</TextBox.ToolTip>
</TextBox>

Related

How to make everything opacity=0.5 aside from customdialogbox

Im new in WPF and Im creating a custom dialog (or a message box) in WPF.
My problem is I cant change everything behind my custom dialog to Opacity=0.5. Only the form who called the custom dialog..
I just want to show my dialog box like in Windows 8.1
Anything would be highly appreciated.
TIA!
Set a property in your MainWindow that controls Opacity of the LayoutRoot. This will change the opacity of your App when the dialog is displayed.
Sample code:
<Grid Name="LayoutRoot" Opacity="{Binding MainWindowOpacity}">
<StackPanel>
<Button Click="Button_Click" Content="Click Me to Show Dialog"/>
<TextBlock Text="WPF" FontSize="72" Margin="50" Foreground="Orange" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
and
public partial class MainWindow : Window
{
public double MainWindowOpacity
{
get { return (double)GetValue(MainWindowOpacityProperty); }
set { SetValue(MainWindowOpacityProperty, value); }
}
// Using a DependencyProperty as the backing store for MainWindowOpacity. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MainWindowOpacityProperty =
DependencyProperty.Register("MainWindowOpacity", typeof(double), typeof(MainWindow), new PropertyMetadata(1.0));
public MainWindow()
{
InitializeComponent();
DataContext = this;
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//if (MainWindowOpacity < 1) MainWindowOpacity = 1.0;
//else MainWindowOpacity = 0.5;
MainWindowOpacity = 0.5;
// show dialog
// boilerplate code from http://stackoverflow.com/questions/6417558/modal-dialog-not-showing-on-top-of-other-windows
Window window = new Window()
{
Title = "WPF Modal Dialog",
ShowInTaskbar = false, // don't show the dialog on the taskbar
Topmost = true, // ensure we're Always On Top
ResizeMode = ResizeMode.NoResize, // remove excess caption bar buttons
Owner = Application.Current.MainWindow,
Width = 300,
Height = 200
};
window.ShowDialog();
MainWindowOpacity = 1.0;
}
}
and the result:
You could add a static method to App.xaml like this:
public partial class App : Application
{
public static void SetWindowsToOpacity(double dOpacity, Window ignoreWindow)
{
foreach (Window win in System.Windows.Application.Current.Windows)
{
if (win != ignoreWindow)
{
win.Opacity = dOpacity;
}
}
}
}
Pass in the Dialog window that you do not want to be set to lower opacity and an opacity value for all the rest.
Call the method like this:
private void button_Click(object sender, RoutedEventArgs e)
{
Window1 dialog = new Window1();
App.SetWindowsToOpacity(0.5, dialog);
dialog.ShowDialog();
App.SetWindowsToOpacity(1.0, null);
}

Turn off autoComplete in Combobox in wpf

I am using .NET framework 4.0 to build my application.
I have a combobox in which I want to turn off suggest-append mode of combobox. Instead I want suggest-only mode.
In many questions users ask for turning autoComplete feature off and everywhere I got the same answer. i.e. set IsTextSearchEnabled to False.
When IsTextSearchEnabled = True
When IsTextSearchEnabled = False
What I want is :
When User Presses Enter on the Combobox I want the Item to be appended to the textbox of the combobox.
Is this thing possible in WPF?
Like promised here is the demo. As you can see I did what I explained in my comments. I listened to text changed event.
Check it out:
<Grid>
<local:MyComboBox x:Name="comboBox" IsEditable="True"
VerticalAlignment="Center"
IsTextSearchEnabled="True">
<ComboBoxItem>hello</ComboBoxItem>
<ComboBoxItem>world</ComboBoxItem>
<ComboBoxItem>123</ComboBoxItem>
</local:MyComboBox>
</Grid>
public class MyComboBox : ComboBox
{
private string myValue;
private bool needsUpdate;
public override void OnApplyTemplate()
{
TextBox tbx = this.GetTemplateChild("PART_EditableTextBox") as TextBox;
tbx.PreviewKeyDown += (o, e) =>
{
this.needsUpdate = true;
};
tbx.TextChanged += (o, e) =>
{
if (needsUpdate)
{
myValue = tbx.Text;
this.needsUpdate = false;
}
else
{
tbx.Text = myValue;
}
};
base.OnApplyTemplate();
}
}

Silverlight TextBox VisibilityChanged event

I'm working on a Windows Phone app using Silverlight C# and XAML. My page contains a ListBox which renders a list of databound objects that the user can manipulate, i.e. add/rename/delete.
I've got it working that the add/rename of items is done in-place, i.e. by swapping a TextBlock for a TextBox depending on the state of the object (bool IsEditable property) and making use of a parameterized VisibilityConverter to manage the opposite Visibility states.
<UserControl.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" True="Visible" False="Collapsed"/>
<local:VisibilityConverter x:Key="InvertedVisibility" True="Collapsed" False="Visible"/>
</UserControl.Resources>
...
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}" />
<TextBox Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}"/>
The thing is that I also want the TextBox to automatically grab focus when it becomes visible, so that the on-screen keyboard pops up without the user having to tap the TextBox.
Since there's no VisibilityChanged event on a regular TextBox, I subclassed TextBox to TextBox2 and added my own:
public class TextBox2 : TextBox
{
public TextBox2()
{
DefaultStyleKey = typeof(TextBox);
}
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged",
typeof(string),
typeof(TextBox2),
new PropertyMetadata("Set the VisibilityChanged event handler"));
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public new Visibility Visibility
{
get
{
return base.Visibility;
}
set
{
if (base.Visibility != value)
{
base.Visibility = value;
VisibilityChanged(this, new EventArgs());
}
}
}
}
Now my XAML looks like this:
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}"/>
<local:TextBox2 Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}" VisibilityChanged="ListEdit_VisibilityChanged"/>
And the event handler like this:
void ListEdit_VisibilityChanged(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.Visibility == System.Windows.Visibility.Collapsed)
return;
textBox.Focus();
}
The TextBox2 renders properly and behaves just like a TextBox at runtime, but my VisibilityChanged event handler is not firing when the databinding flips the value of IsEditable.
IsEditable defines the Visibility and the TextBox2 does become visible correctly, so the databinding is working.
I can cause the event to fire programmatically by getting hold of the TextBox2 instance and setting the Visibility of that in code. That also works.
But this databinding scenario being responsible for setting the Visibility seems not to work.
Any ideas why not?
Here are 2 solutions that I use.
Solution 1 needs no sub class, but solution 2 is more reusable.
1. You can subscribe to the Loaded event of the TextBox, and force a focus, like so:
void TextBox_Loaded_Focus(object sender, System.Windows.RoutedEventArgs e) {
ForceFocusControl((Control)sender);
}
void ForceFocusControl(Control control) {
control.Focus();
if (FocusManager.GetFocusedElement() != control) {
Dispatcher.BeginInvoke(() => ForceFocusControl(control));
}
}
This solution goes into a recursive loop though, you might want to add some checks to make it safer.
2. Keep your subclass TextBox2, and rather create a private MyVisibility dependency property that you bind to the Visibility property of the base class, but also specify a DependencyProperty_Changed handler, like so:
/// <summary>
/// <see cref="TextBox2"/> will focus itself when it becomes visible.
/// </summary>
public sealed class TextBox2 : TextBox {
public TextBox2() {
SetBinding(TextBox2.MyVisibilityProperty, new Binding("Visibility") { Source = this });
}
static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register(
/* name = */ "MyVisibilityProperty",
/* property type = */ typeof(Visibility),
/* owner type = */ typeof(TextBox2),
/* meta = */ new PropertyMetadata(MyVisibilityProperty_Changed));
static void MyVisibilityProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
TextBox2 TextBox2 = (TextBox2)d;
if (TextBox2.Visibility == Visibility.Visible) {
TextBox2.Focus();
}
}
}
This is how my TextBox2 class looks now:
public class TextBox2 : TextBox
{
public event VisibilityChangedEventHandler VisibilityChanged;
public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
"VisibilityChanged", typeof(VisibilityChangedEventHandler), typeof(TextBox2), null);
static readonly DependencyProperty MirrorVisibilityProperty = DependencyProperty.Register(
"MirrorVisibility", typeof(Visibility), typeof(TextBox2), new PropertyMetadata(MirrorVisibilityChanged));
public TextBox2()
{
SetBinding(TextBox2.MirrorVisibilityProperty, new Binding("Visibility") { Source = this });
}
static void MirrorVisibilityChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((TextBox2)obj).VisibilityChanged(obj, null); // raise event
}
}

Databinding TextBlock Runs in Silverlight / WP7

I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!
I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.

WPF- Problem with TextBox in DataTemplate

I am working on an application where Repository objects are displayed via a DataTemplate that contains a modified version of a TextBox, which supports binding to the SelectionStart, SelectionLength, and VerticalOffset.
The DataTemplate looks like this:
<DataTemplate DataType="{x:Type m:Repository}">
<controls:ModdedTextBox
x:Name="textBox" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionStart="{Binding SelectionStart, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionLength="{Binding SelectionLength, UpdateSourceTrigger=PropertyChanged}"
BindableVerticalOffset="{Binding VerticalOffset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
The problem is that when I change the Repositorythat is currently being displayed; the SelectionStart, SelectionLength, and VerticalOffset all seem to be getting set to 0, even when those properties of the Repository object are not 0.
I think that this is happening in the instant before the text is displayed when the SelectionStart, SelectionLength, and VerticalOffset can not be more than 0. This does not only set the actual properties of the TextBox to zero, but also updates the bindings and sets the properties of the Repository object to zero.
Is there any way that I can prevent this from happening?
--Edit--
I don't know if posting dl links to projects is a no-no or not on SO, but here is a link to a project I created to demonstrate the problem I am having: http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip
When you run the demo-app the selection you can click the "Switch Repository" button to change the repository that is displayed in the textbox. If you look to the right of the textbox the current repository's properties all get set to zero when you switch to the other one.
A difference between this demo and my actual app is that in my app repositories will be switched via hotkeys, not a button.
The problem is due to the fact that the bindings are evaluated in serial, and when the Text property is changed it causes all selection information to be removed (you can see this by putting breakpoints on your ModdedTextBox event handlers). As the BindableSelection... bindings are still active at that point, it causes the selection information to be reset.
Depending on the exact behaviour you want there is probably a way to work around this, but you would need to know a little more detail...
Edit in response to comments:
This solution isn't exactly answering your original question, and it probably isn't great practice, but it does at least work...
Try altering your ModdedTextBox so that instead of exposing bindable properties for the selection information, expose a single DP of type Repository and bind to that:
<local:ModdedTextBox
x:Name="textBox"
Repository="{Binding CurrentRepository}"
TextWrapping="Wrap"
/>
Then handle the changed event on your DP to set the text box properties:
public static DependencyProperty RepositoryProperty =
DependencyProperty.Register("Repository",
typeof(Repository), typeof(ModdedTextBox), new PropertyMetadata(null, OnRepositoryChanged));
public Repository Repository
{
get { return (Repository)base.GetValue(RepositoryProperty); }
set { base.SetValue(RepositoryProperty, value); }
}
private static void OnRepositoryChanged(DependencyObject senderObject, DependencyPropertyChangedEventArgs e)
{
var sender = (ModdedTextBox)senderObject;
var oldRepository = e.OldValue as Repository;
var newRepository = e.NewValue as Repository;
if (oldRepository != null)
{
oldRepository.Text = sender.Text;
oldRepository.SelectionStart = sender.SelectionStart;
//etc
}
if (newRepository != null)
{
sender.Text = newRepository.Text;
sender.SelectionStart = newRepository.SelectionStart;
//etc
}
}
This is essentially removing the serial nature of the binding evaluation.
Note: You could also achieve the same using attached properties, which would be better than subclassing TextBox, but this is closer to your original attempts so I figure its easier to explain!
Here is a re-write of the other solution. This one takes into account the text property not being bound before the other properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool isBindingComplete = false;
public SelectionBindingTextBox()
: base()
{
this.SelectionChanged += this.OnSelectionChanged;
this.TextChanged += this.OnTextChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (isBindingComplete)
{
this.BindableSelectionStart = this.SelectionStart;
this.BindableSelectionLength = this.SelectionLength;
}
}
private void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!isBindingComplete)
{
SetupSelection();
}
isBindingComplete = true;
}
private void SetupSelection()
{
// this.Focus();
this.SelectionLength = this.BindableSelectionLength;
this.SelectionStart = this.BindableSelectionStart;
}
}
}
Well update of binding depends on the order in which WPF or Silverlight Engine will evaluate, looks like your SelectionStart and SelectionEnd bindings are updated before Text, so when Text gets changed, SelectionStart and SelectionEnd are both changed back to zero.
The only way is to hook for TextChanged event and refresh the bindings of SelectionStart and SelectionEnd or in WPF you can extend textbox as follow
public class MyTextBox : TextBox{
protected override OnTextChanged(TextChangedEventArgs e){
BindingExpression be = this.GetBindingExpression(SelectionStartProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(SelectionEndProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(VerticalOffsetProperty);
if(be!=null){
be.UpdateTarget();
}
}
}
Well here there is a trick, you still have to change above logic to fit in your logic because everytime text updates this will update binding, so you have to find out when to refresh these bindings. Because this will consistantly fail to change your textbox's value in runtime as text will modify and selection will goto previous selection only.

Resources