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.
Related
From MSDN entry for Control.ResetText():
Resets the Text property to its default value.
Stupid question, but I can't find where I can set this "default value". When I invoke ResetText() on TextBox all text is simply cleared.
The default value used by ResetText in the TextBoxcontrol is not configurable and it happens to be the string.Empty so that's why when you invoke the method the text is cleared.
However, since the ResetText method is virtual you can do the following:
class MyTextBox : TextBox
{
public override void ResetText()
{
this.Text = "MyDefaultText";
}
}
Now, you just need to use your MyTextBox instead of the TextBox control. This is of course complicating things just for the sake of setting the Text property, so you are better of by assigning your default text directly to the Text property if you have no requirements to support a default value specifically through the use of ResetText.
That method is used within the designers. So unless you are not writing a designer with this control you need not worry about it. Runtime default value is empty string.
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.
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();
I have a TextBox bound to a ViewModel's Text property with the following setup:
Xaml
<TextBox Text="{Binding Text}"/>
C#
public class ViewModel : INotifyPropertyChanged
{
public string Text
{
get
{
return m_Text;
}
set
{
if (String.Equals(m_Text, value))
{
return;
}
m_Text = value.ToLower();
RaisePropertyChanged("Text");
}
}
// Snip
}
When I type some stuff in to the TextBox it successfully sets the Text property on the ViewModel. The problem is that WPF ignores the property changed event that is raised by it's own update. This results in the user not seeing the text they typed converted to lowercase.
How can I change this behaviour so that the TextBox updates with lowercase text?
Note: this is just an example I have used to illustrate the problem of WPF ignoring events. I'm not really interested in converting strings to lowercase or any issues with String.Equals(string, string).
You can achieve this by raising the event in a seperate dispatcher call using Dispatcher.BeginInvoke
Define a delegate:
private delegate void RaisePropertyChangedDelegate(string property);
Then use the following to raise the event
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new RaisePropertyChangedDelegate(RaisePropertyChanged),
"Text");
Apparently this is fixed in WPF 4.0 in .NET Framework 4.0 released in 2010: Karl Shifflett's blog
I remember Rocky Lhotka complaining about this very behaviour on an old episode of .NET Rocks. (Searches for half an hour ...) ah, here we are. Episode 169 from March, 2006:
So if you have got a detailed Form in
Windows Forms then you bind all of
your properties to those different
text boxes and the user is typing
things into a text box and tabs off,
of course that value gets put into
your object but your object then might
change the value, may be there is some
sort of a manipulation that says all
letters must be upper case. It’s a
business rule so it goes in your
object. If you do that it won’t show
up in the UI. In other words the user
can type a, b, c, type in lower case,
tab off. The lower case a, b, c will
stay in the text box. Then later when
they change some other field, then
they will keep in mind the object to
upper case the value, right? So the
object has an uppercase a, b, c the UI
is incorrectly showing a lowercase,
the user then changes some other
control and tabs off that control, all
of a sudden the a, b, c in uppercase
shows up in the text box that they
weren’t on.
Rocky doesn't actually suggest a solution to the problem, and I'd hazard that if he hasn't worked it out maybe there's no good answer. Perhaps you need to subscribe to the PropertyChanged event on your object from the code-behind and manually refresh the binding when the property in question has changed.
ps. This isn't directly answering your question, but in the example you've given you could set the CharacterCasing on the TextBox so that it only accepts lower-case characters.
You need to force an update of the binding target (see this post on MSDN). However, this was changed/broken in WPF4, so you have to force an update on the source instead (see this post).
I’m porting a WPF app to silverlight 2, and have come across several WPF features which are presently missing from SL. Could anyone help me with equivalents or suggest workarounds.
I want to handle clicks and double clicks on a textbox embedded in a list box. The WPF implementation uses PreviewMouseLeftButtonDown/Up on a listbox control. How can this be done in silverlight, it seems that PreviewMouseLeftButtonDown/Up are missing in silverlight.
I want to handle button presses (F2/Delete) on a textbox embedded in a list box. The WPF implementation uses PreviewKeyDown on a textbox control which embedded as an item in a listbox. It seems that PreviewKeyDown is missing in silverlight. The KeyDown event handler does not seem to get invoked.
I want to change some appearance properties of a textbox depending on the value of some custom attached properties. The WPF implementation uses a DataTrigger to do this. How can this be done in silverlight. It seems that DataTriggers are missing in silverlight.
I want to change the width of a text box depending on the Actual Width of the listbox in which the text box is contained. The WPF implementation uses RelativeSource binding. What is the silverlight equivalent, or workaround for this.
For item 1 and 2, the best way to get access to these input events is to create a custom TextBox deriving from the built in TextBox. Then you can override the OnKeyDown and OnMouseLeftButton down. From there you can either call the necessary code, or fire a new event. e.g.:
public class MyTextBox : TextBox
{
public event MouseButtonEventHandler MySpecialMouseLeftButtonDown;
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (MySpecialMouseLeftButtonDown != null)
{
MySpecialMouseLeftButtonDown(this, e);
}
base.OnMouseLeftButtonDown(e);
}
}
Similarly with OnKeyDown.
I'm more familiar with Silverlight than the full WPF. Please considier my responses accordingly.
For number 2. For many keys, I check on KeyUp and KeyDown. I use KeyDown while trying to watch the entire time that the key is held down and KeyUp when it was used just once. You should know this was for a game without an individual text box.
For item 4, you can bind both the listbox width and the textbox width to a static resource's property so that it acts as a router for the binding. You could also use a value converter that you initialize with a reference to the listbox, then use the converter for your textbox width.
For item 3, you could use a similar approach.