Updating WPF TextBox properties after setting Text - wpf

When setting the Text property of a WPF TextBox control, other properties that should also change (as a side effect) do not change. In particular, I would like to check the value of the ExtentWidth property after setting Text, but it does not change. I've tried calling UpdateLayout() to no avail. In Windows.Forms, I would call DoEvents().
OK, here's some code. I put this in the Window_Loaded() event handler. The problem is that textBox.ExtentWidth doesn't change when textBox.Text changes. That doesn't really surprise me. I figure I need to call something like textBox.UpdateLayout() to make it recalculate ExtentWidth, but that didn't help. ExtentWidth does vary depending on what I initialize textBox.Text to in the Window's constructor, but that doesn't help me. I need to set several different Text values and get the corresponding ExtentWidth for each.
string initText = textBox.Text; // "textBox"
double extentWidth = textBox.ExtentWidth; // 39.3
textBox.Text = "short text";
extentWidth = textBox.ExtentWidth; // 39.3
textBox.Text = "Long enough to make a difference, eh?";
extentWidth = textBox.ExtentWidth; // 39.3

I found a solution to the specific problem of getting TextBox.ExtentWidth to change after setting Text. Setting Text will raise the LayoutUpdated event, and you can get the new value of ExtentWidth in a handler for LayoutUpdated.
I used this fact to create a subclass of WPF TextBox that displays an ellipsis when the text is too long for the visible area. I wrote a CodeProject article about it here.

Related

Listening to changes in the Text property of the WPF TextBlock control

For a WPF TextBlock, setting the TextTrimming to TextTrimming.CharacterEllipsis will cause it to automatically cut off the text before it overflows and add some ellipses to the end. This article shows how to check if the text is being trimmed and automatically show the full text in a tooltip when it is.
It does this, without subclassing TextBlock, by registering an event handler that listens to the SizeChanged event:
EventManager.RegisterClassHandler(
typeof( TextBlock ),
FrameworkElement.SizeChangedEvent,
new SizeChangedEventHandler( OnTextBlockSizeChanged ),
true );
The trouble is, this only reacts to size changed events - it works fine if the text overflows because you shrank the control, but not if it overflows because you changed the text.
Unfortunately, although the TextBlock does have a SizeChangedEvent, it doesn't have a TextChangedEvent. I thought of listening to the TargetUpdated event:
EventManager.RegisterClassHandler(
typeof(TextBlock),
Binding.TargetUpdatedEvent,
new EventHandler<DataTransferEventArgs>(OnTextBlockTextChanged),
true);
But that didn't have any discernable effect, even with the NotifyOnTargetUpdated property set to true. I also tried overriding the metadata on the TextProperty but it seems that can really only be done in its static constructor - in this case the TextBlock's static constructor. Is there any way of achieving this without subclassing TextBlock?
You can use DependencyPropertyDescriptor:
var descriptor = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
descriptor.AddValueChanged(...);
PS. Why on Earth TextBlock does not have an IsTrimmed property is beyond me.

WPF - Overriding IsVisible on my custom control

I have a custom control (an Adorner) that wraps another control (a textbox).
public class MyCustomAdorner : Decorator
{
...
public MyCustomAdorner()
{
Child = new TextBox();
}
}
I want to be able to override the VisibilityChanged so that the MyCustomAdorner's event is only fired if the Child's visiblity changes, not the actual decorator. How would I go about this?
On a first approach I would try to bind the Adorner's Visibility to the TextBox's Visibility (not sure if this one works). This way, if the textbox changes visibility, the adorner will follow. If you bind them two way, then it will work the other way around too. So if you don't want it to work both ways, make sure you don't set the Adorner's Visibility.
If binding the Visibilities together doesn't work, you can try to declare a new property (for example a bool), that manages the visibilities, and bind the two Visibilities to that bool through a Converter. And when you want to change something around the Visibilities, you set this bool to a new value.
Overriding the VisibilityChange event doesn't sound good to me, also I'm not sure if you can even do that...

What is the worst gotcha in WPF?

I've started to make myself a list of "WPF gotchas": things that bug me and that I had to write down to remember because I fall for them every time....
Now, I'm pretty sure you all stumbled upon similar situations at one point, and I would like you to share your experience on the subject:
What is the gotcha that gets you all the time? the one you find the most annoying?
(I have a few issues that seem to be without explanation, maybe your submissions will explain them)
Here are a few of my "personnal" gotchas (randomly presented):
For a MouseEvent to be fired even when the click is on the "transparent" background of a control (e.g. a label) and not just on the content (the Text in this case), the control's Background has to be set to "Brushes.Transparent" and not just "null" (default value for a label)
A WPF DataGridCell's DataContext is the RowView to whom the cell belong, not the CellView
When inside a ScrollViewer, a Scrollbar is managed by the scrollviewer itself (i.e. setting properties such as ScrollBar.Value is without effect)
Key.F10 is not fired when you press "F10", instead you get Key.System and you have to go look for e.SystemKey to get the Key.F10
... and now you're on.
Always watch the output window for
binding errors. Ignoring the output
window is a recipe for tears.
Use PresentationTraceOptions.TraceLevel="High" in a binding to get verbose binding information when debugging binding failures.
Make static, unchanging resources such as brushes PresentationOptions:Freeze="True" to save resources at runtime.
Use the WPF DataGrid as a datagrid. Modifying it to behave like Excel is a massive pain in the butt.
BindingList<T> does not play well with CollectionViewSource. Expose ObservableCollection<T> from your viewmodels instead.
The internet supplies half a dozen different ideas for displaying CueBanner text in a WPF textbox. They are all broken.
1) One that used to get me every half an hour when I was making my transition from WinForms: use TextBlock instead of Label when putting random text on the UI (or don't use any tag at all, if the text is static)!
2) DataTriggers/Triggers can't be put into Control.Triggers, but have to go into Control.Styles/Style/Style.Triggers
3) Property's type must implement IList, not IList<T>, if the property is to be recognized by XAML as a collection property.
4) Bindings capture exceptions.
5) Use singleton converters/static converter class, so you don't have to create a new converter every time you use it.
6) A type for default value of DependencyProperty has to be clearly specified: 0u as uint, (float) 0 as float, 0.0 as double...
7) It matters if the control's property definitions are before or after its content.
8) NEVER use PropertyMetadata to set a default value of reference type DependencyProperty. The same object reference will be assigned to all instances of the owning class.
When first starting out, the main gotchas that would get me would be
Lists not updating due to forgetting
to use ObservableCollection
Properties not being updated either
forgetting to add OnPropertyChanged
or incorrectly typing the property
name
Recently I have stumbled across these issues
Application failing to start due to
corrupt font cache
StringFormat localization issues
If enabled, Button.IsCancel assigns false to Window.DialogResult but Button.IsDefault no.
They are so similar and for me it seemed intuitive at first that both should close dialog. I usually break MVVM and fix this in code-behind
Button.IsCancel + Command = Dialog won't close (Window.DialogResult left unassigned) but Command executes
As I understand it: If IsCancel had higher priority than Command then on Esc it would assign 'false' to DialogResult and Command won't be called. Or, if Command would have higher priority then it would be called first and DialogResult would be assigned. I don't understand how it is skipped?
Binding swallows exceptions!
It not only steals time while debugging it is also wrong from the OOP point of view because if exception is thrown it means that something exceptional had happened somewhere in our system (anything from wrong data supply to unauthorized access to memory failure) so it can be handled only if you know what to do. You can't just catch(Exception){} catch 'em all and then ignore. If there is unknown exception in program it should notify user, log and close not pretend like everything is ok...
HeaderContent can have only one child control and has no padding
Everything should have padding even logical controls (containers), right? I think it is inconsistent. What do you think?
If you set focus to ListBox via FocusManager.FocusedElement you still won't be able to switch it's content with keyboard because focus is set to ListBoxes frame not it's content. I think I don't know other UI API that would expose something like controls frame to UI programmer it should be encapsulated from us because abstractly ListBox represents a list, it is just a list of things not a list of things in a box. ok it has box in its name but still... We almost have two different controls here.
MVVM not breaking fix
ListBox.IsSynchronizedWithCurrentItem by default is false so if you assign different value or null to ItesSource then SelectedItem still holds old value until user selects something from a new list. It could mess up CanExecute for example. Need to set it every time by hand.
No binding exposed in PasswordBox results in time waste and dirty hacks... But still it has a string property PasswordBox.Password exposed so don't even try to argue about security because Snoop...
It is not a gotcha but table layout is so IE6 IMO. Container design helps separate content from its layout.
Because every time I need to change something in places I need to mess up with Grid.Row and Grid.Column. Yes, we have DockPanel, StackPanel and others but you can't do some column alignment inside of them. (And DockPanel is like completely separate gotcha) If UniformGrid would be more customizable it would be ideal I think. You always need to choose between Grid and Panels and usually if you gain something you loose something else.
I got a pretty nifty one last week:
When Templating a RichTextBox, the event handling inside the template follows a strange route that has nothing to do neither with tunnelling nor bubbling
e.g.: In the case of an event that is supposed to tunnel: the event first tunnels through the ContentPresenter, then it tunnels back from the top of the template.
see my question on the subject
ToolTips and ContextMenus not sharing the DataContext of its owner? I think that gets everyone at first
There is no clean way to handle validation in WPF, I am not a fan of magic string which IDataErrorInfo offers by default:
public string this[string columnName]
{
if (columnName == "FirstName")
{
if (string.IsNullOrEmpty(FirstName))
result = "Please enter a First Name";
}
}
However, I have tried many frameworks like SimpleMVVM, FluentValidation and MVVMValidation and BY FAR MVVM Validation is the best getting to do stuff like:
Validator.AddRule(() => RangeStart,
() => RangeEnd,
() => RuleResult.Assert(RangeEnd > RangeStart, "RangeEnd must be grater than RangeStart");
My personal favorite is this one:
public double MyVariable
{
get { return (double)GetValue(MyVariableProperty); }
set { SetValue(MyVariableProperty, value); }
}
public static readonly DependencyProperty MyVariableProperty = DependencyProperty.Register(
"MyVariable", typeof(double), typeof(MyControl), new UIPropertyMetadata(0));
Try it, once this property is declared it will crash. Why? Because 0 can't be assigned to a double using reflection apparently.
Not really a gotcha but an advice: Use Snoop or something similar, if you don't use it you must be crazy ... Crazy i tell ya!
Binding.StringFormat only works if the type of the target property is string.
TreeView's SelectedItem property is not settable. Instead you have to bind TreeViewItem's IsSelected property to your item's viewmodel and set your selection there.
ListBox's SelectedItem, on the other hand is settable, but item selection is not equal to item focus. If you want to implement proper keyboard navigation along with selecting items from within viewmodel, you have to implement manual focus fix, like:
public void FixListboxFocus()
{
if (lbFiles.SelectedItem != null)
{
lbFiles.ScrollIntoView(lbFiles.SelectedItem);
lbFiles.UpdateLayout();
var item = lbFiles.ItemContainerGenerator.ContainerFromItem(viewModel.SelectedFile);
if (item != null && item is ListBoxItem listBoxItem && !listBoxItem.IsFocused)
listBoxItem.Focus();
}
}
...and call it every time you change selected item from viewmodel:
SelectedFile = files.FirstOrDefault();
viewAccess.FixListboxFocus();

Silverlight - add ValueConverter to Binding object in custom control

I'm building a custom control in Silverlight, extending TextBox. The purpose of the control is to provide a watermark logic (default text, typically used in search boxes). I managed that, when accessing the Text property, it will return string.Empty if Text == Watermark. Indeed, you don't want to consider something like "Enter name here" as a relevant value. When it comes to TwoWay databinding, things get more complicated.
I created a ValueConverter, that takes as parameter the watermark and returns string.Empty if Text == Watermark, Text otherwise. I want the control to be very ease to use, so it would be cool if the client code wouldn't have to specify each time that converter when binding to the Text property. Instead, the converter would be plugged inside the custom control, on the binding object related to the Text property.
I tried the following code, but it crashes because the binding object cannot be modified once it has been assigned. I tried that code in the Load() and OnApplyTemplate() events.
var watermarkedTextBox = (WatermarkedTextBox)dependencyObject;
var textBindingExpression = watermarkedTextBox.GetBindingExpression(TextProperty);
if (textBindingExpression != null)
{
var textBinding = textBindingExpression.ParentBinding;
textBinding.Converter = new WatermarkConverter();
textBinding.ConverterParameter = watermarkedTextBox.Watermark;
watermarkedTextBox.SetBinding(TextProperty, textBinding);
}
So I need to intercept the binding object at the right time (where it's still allowed to modify it). Any ideas ?
Thanks in advance,
Thibaut
All right, discussed this with colleagues, found the optimal solution.
The watermark is defined in the ControlTemplate of the custom control. It's a TextBlock added in the TextBox, hidden on focus, shown if text is empty. Code is much better like that :
No need to play with the Text property and change it under certain conditions to change it to watermark, or change it to string.Empty so the watermark text is never returned (was error prone)
Watermark text style can be directly template bound (TemplateBinding), so it's great, without any C# code, client will be able to customize the appearance of the watermark : color, italicize and more
Offers new possibilities (image watermark textbox almost for free)
See you ;)
I haven't tried it yet but the Silverlight 4 Textbox has a Watermark property.

WPF ToggleButton binding IsChecked when disabled

This is a bit of a wierd problem. (.NET 3.5sp1)
I have a UserControl containing three ToggleButtons, each with IsChecked bound to different dependency properties on the UserControl itself. Two of these default to true, one defaults to false.
On startup of the application, the UserControl itself (and thus its contents) is disabled. When it gets enabled later on, all three buttons appear un-pressed; however the code properties are still in the correct state.
If the buttons are clicked then the properties will toggle properly and the UI (for that button only) will update to reflect the correct state (ie. clicking on a button which appears un-pressed but has a true bound value will show no visible change the first time, but updates the bound value to false). Pressing a "glitched" button for the second time will behave normally (if it toggles on, the button will press in as expected).
If the UserControl is not disabled on startup, then the buttons will appear correctly (according to the state of the properties).
Unfortunately the UserControl is supposed to be disabled on startup, so I can't really start up with it enabled; I'm hoping for an alternate solution. Any ideas?
(I've tried making the properties default to false and then setting them to true in the user control's Load event. Doesn't make any difference.)
The same weird problem happens in .NET 4.0 as well. I noticed that the problem only occurs if you set IsEnabled through code and not if you bind it, so if you can change your code to that instead I believe your problem will be solved.
Otherwise, I believe a workaround is necessary and here is one way to do it. Reset the DataContext for the UserControl the first time IsEnabled is set to True.
public UserControl1()
{
InitializeComponent();
DependencyPropertyDescriptor descriptor =
DependencyPropertyDescriptor.FromProperty(UserControl1.IsEnabledProperty,
typeof(UserControl1));
EventHandler isEnabledChanged = null;
isEnabledChanged = new EventHandler(delegate
{
if (IsEnabled == true)
{
descriptor.RemoveValueChanged(this, isEnabledChanged);
var dataContext = this.DataContext;
this.DataContext = null;
this.DataContext = dataContext;
}
});
descriptor.AddValueChanged(this, isEnabledChanged);
}
I came across a 'somewhat' similar situation and the following SO reply proved to be more appropriate in my case. It save me a lot of troubles and so, just in case anyone looking at this solution hasn't noticed it, I think a reference to it should be useful here.
WPF ToggleButton incorrect render behavior
Cheers
Mansoor

Resources