|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Table of Contents
s Inside the TextBoxCreating a Silverlight Super TextBox (ComboBox, Masked TextBox and More)This document (with the included source code) demonstrates how to develop a “Super
Have fun! IntroductionMany controls were included with the recent release of Silverlight 2b1, however a few controls were noticeably missing. For instance there is no Since it will be very hard to develop a business application without such controls, we need to come up with some custom controls. The control presented here (
Figure 1
By making the code public, I hope others can help to further improve the concept. When such improvements become available, I'll post them. I also have a working Tab Control. Once it is more “mature”, I might post that as well. Selectively I'll explain some of the features, workarounds and problems I encountered while creating this control. (Please review the source code and code samples for complete information.) Where possible, I'll refer to links that helped me out and/or other people who came up with suggestions or sample code. Some of the code is from the Microsoft source code, please note their license requirement (it is in the files to which it applies): // Copyright © Microsoft Corporation.
// This source is subject to the Microsoft Source License for Silverlight Controls(March 2008 Release).
// Please see http://go.microsoft.com/fwlink/?LinkID=111693 for details.
On my personal code I have no license requirement. Please use as you see fit! Just as an example, for the longest time I have searched for a way to have the client display a byte array as an Image (using WCF, we receive an Image as a A few days ago I figured out how it is done in Silverlight 2.0 (I simply bind public ImageSource ThumbImage
{
get
{
if (Thumb == null)
return null;
if (Thumb.Bytes.IsEmpty())
return null;
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(new MemoryStream(Thumb.Bytes));
return bitmap;
}
}
I frequently use extension methods (like Silverlight makes it easier to develop your own controls, so I spent a few days filling the gap in my “control library”. Microsoft was nice enough to release a The first thing I noticed was that Microsoft includes a Watermarked
Figure 2
However, strangely enough you cannot inherit your control from Watermarked For this reason, I have made a copy of this control (labelled protected virtual void SetStyle()
{
}
Now we can override public class STextBox : SWatermarkedTextBox
{
}
It is also interesting to see that Included with the sample code is a simple Page.XAML (notice the <UserControl x:Class="SilverlightTextBox.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:g="clr-namespace:SQControls;assembly=SQControls"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="#FF3C5DA2">
</Grid>
</UserControl>
Within the <Canvas x:Name="LayoutRoot" Background="#FF3C5DA2">
</Canvas>
Figure 2 is the result of these lines: <g:STextBox x:Name="Test1" Width="150" Height="25" Margin="5" FontSize="15"
Text="Some text" IsEnabled="False"/>
<g:STextBox x:Name="Test2" Width="150" Height="25" Margin="5" FontSize="15"
Watermark="Required field!" />
Buttons Inside the TextBoxI have always enjoyed the 3rd party controls that would enable me to include all kinds of In XAML, it looks like this: <g:STextBox x:Name="Test6" Width="150" Height="25" Margin="5"
FontSize="15" Text="Sample" >
<g:STextBox.Content>
<StackPanel Orientation="Horizontal" Width="40" HorizontalAlignment="Left">
<Button x:Name="BLookup" Width="20" Height="20" Content="…" />
<Button x:Name="BHelp" Width="20" Height="20" Content="?" />
</StackPanel>
</g:STextBox.Content>
</g:STextBox>
And it displays like this:
Figure 3
One of the (failed) attempts I made was to have that collection respond to the usual Mouse events on the buttonElement.IsHitTestVisible = true;
buttonElement.IsTabStop = tabStop;
if buttonElement.Content is Control)
{
(buttonElement.Content as Control).IsHitTestVisible = true;
(buttonElement.Content as Control).IsTabStop = tabStop;
}
Panel pnl = buttonElement.Content as Panel;
if (pnl != null)
{
foreach
(UIElement ctrl in pnl.Children)
{
if (ctrl is Control)
{
(ctrl as Control).IsHitTestVisible = true;
(ctrl as Control).IsTabStop = tabStop;
}
}
}
That works fine to disable the <g:STextBox x:Name="Test6" Width="150" Height="25" Margin="5" FontSize="15"
Text="Sample" ContentClick="Test6_ContentClick" >
<………>
Hovering over one of the private void Test6_ContentClick(object sender, RoutedEventArgs e)
{
if (sender == BLookup)
"Lookup goes here".Alert();
if (sender == BHelp)
"Help goes here".Alert();
}
When adding a Adding these controls to the parent of Predefined Buttons, ComboBox and Erase Inside the TextBoxThe following XAML code demonstrates the predefined <g:STextBox x:Name="Test3" Width="150" Height="25" Margin="5" FontSize="15"
Text="Sample" EraseButtonName="ButtonErase" >
<g:STextBox.Content>
<Button x:Name="ButtonErase" Width="20" Height="20"
HorizontalAlignment="Left" Content="X" />
</g:STextBox.Content>
</g:STextBox>
Figure 4
The following XAML code demonstrates the predefined <Canvas>
<g:STextBox x:Name="Test5" Width="150" Height="25" Margin="5" FontSize="15"
Text="Sample" DropDownButtonName="ButtonDD" DropDownPosition="BottomRight">
<g:STextBox.Content>
<Button x:Name="ButtonDD" Width="20" Height="20" Content="6"
FontFamily="Webdings" HorizontalAlignment="Right"/>
</g:STextBox.Content>
<g:STextBox.ListBoxContent>
<ListBox x:Name="DDBox" Height="100" >
<ListBoxItem Content="Option1"/>
<ListBoxItem Content="Option2"/>
<ListBoxItem Content="Option3"/>
<ListBoxItem Content="Sample"/>
<ListBoxItem Content="Option4"/>
<ListBoxItem Content="Option5"/>
<ListBoxItem Content="Option6"/>
</ListBox>
</g:STextBox.ListBoxContent>
</g:STextBox>
</Canvas>
Figure 5
Some additional features:
For the most part, keyboard control works as you would expect. Issues/ProblemsThere are a number of issues in the current setup: Setting Selected Item in ListBoxI was initially unable to make the correct initial item “Selected” in the dropdown. I fixed this with a Using the Mouse-WheelUsing the Tab Sequence on Dropdown ListBoxThe weird thing with the current private static void SyncTabStop(UIElement me, bool vis)
{
// for SL2.0b1, Collapsed with IsTabStop is a big issue...
if (me == null) return;
if (me is Control)
{
(me as Control).IsTabStop = vis;
}
else if (me is Border)
{
SyncTabStop((me as Border).Child, vis);
}
else if (me is Panel)
{
if (vis)
(me as Panel).Children.StartTabKey();
else
(me as Panel).Children.StopTabKey();
}
}
... where Sadly once I make the Setting Focused Item in ListBoxThis might sound similar but is not. When you look carefully at Figure 5, you see that the So the strange behavior is that you open the Now I also had a desire to retrieve the actual Thirdly I would like to get the I did figure out the code that I would like to add to the public void Set_tabOnceActiveElement()
{
_tabOnceActiveElement = GetListBoxItemForObject(Items[SelectedIndex]);
if (_tabOnceActiveElement!=null)
_tabOnceActiveElement.Focus();
}
public ListBoxItem Get_ListBoxItemForObject(object value)
{
return GetListBoxItemForObject(value);
}
public ScrollViewer Get_ElementScrollViewer()
{
return ElementScrollViewer;
}
Having a Silverlight Headache!!Failed Attempt 1Normally I would have been able to make these simple adjustments by using reflection, and initially I attempted to do just that. BUT in Silverlight, reflection is not allowed to access any private/internal member and/or to Invoke any such method. That was much to my surprise but is further outlined here:
That, to me is just very inconvenient and simply not in the .NET spirit. I can only assume there is some underlying major security risk that requires this limitation. It would not have been so bad if the Microsoft controls like Failed Attempt 2I subsequently tried to modify the Failed Attempt 3Since I was successful with making a copy of the However Formatting Behavior of STextBoxBy just going through some normal form scenarios, I was quickly stuck on the next issue. I had several fields which would not display the way I liked. Formatting With a Value ConverterOn an public class DBCompanyEnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (CompanyEditor.Settings != null)
{
string name = (from e in CompanyEditor.Settings.CompanyEnums
where e.DBCompanyEnumId == (byte)value
select e.DBCompanyEnumName).FirstOrDefault();
return name;
}
return "Type " + value.ToString();
}
public object ConvertBack(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
int Id = (from e in CompanyEditor.Settings.CompanyEnums
where e.DBCompanyEnumName == (string) value
select e.DBCompanyEnumId).FirstOrDefault();
return (byte)Id;
}
}
BTW, in XAML you hook it up like this: <UserControl.Resources>
<c:DBCompanyEnumConverter x:Key="CTypeFormatter"/>
</UserControl.Resources>
<……>
<g:STextBox x:Name="CompanyType" Text="{Binding CompanyType, Mode=TwoWay,
Converter={StaticResource CTypeFormatter} }"
BorderThickness="0" DropDownButtonName="TypeDropDown"
StrictDropDown="True">
<g:STextBox.Content>
<g:SButton x:Name="TypeDropDown" Width="16" Height="16" Content="6"
FontFamily="Webdings" HorizontalAlignment="Right"/>
</g:STextBox.Content>
<g:STextBox.ListBoxContent>
<ListBox Width="120" Height="140" DisplayMemberPath="DBCompanyEnumName">
</ListBox>
</g:STextBox.ListBoxContent>
</g:STextBox>
Formatting with a String.Format ExpressionUsing standard .NET formatting, you can do a lot (when you use the <g:STextBox x:Name="Markup" Format="###.##%" Value="{Binding Markup,
Mode=TwoWay}" BorderThickness="0" HorizontalAlignment="Right" Width="90"/>
Figure 6
private void OnValueChanged()
{
//
also See OnLostFocus for reverse mapping...
if
(Value == null)
base.Text = "";
else if (!String.IsNullOrEmpty(_Format))
{
// tips see http://blog.stevex.net/index.php/2007/09/28/string-formatting-faq/
// and http://john-sheehan.com/blog/index.php/net-cheat-sheets/
base.Text = String.Format(TextBoxCulture,"{0:" + _Format + "}", Value);
}
else
base.Text = Value.ToString();
}
When Focus is lost, if (!String.IsNullOrEmpty(_Format))
{
//
here handle any special "DEFORMATTING" tricks so the generic
Convert.ChangeType will work...
if (_Format.Contains("#%") || _Format.Contains("9%") || _Format.Contains("#'%")
|| _Format.Contains("9'%")) // % sign optional
{
input = input.Replace("%", "");
<....>
}
}
<validation code>
if (error == null)
{
Value = Convert.ChangeType(input,
Value.GetType(), TextBoxCulture);
//
Value setter can also throw validation errors !!
}
Masked Behavior of STextBoxCombined with a <g:STextBox x:Name="Test4" Width="150" Height="25" Margin="5" FontSize="15"
Text="8001234567" Mask="(000)000-0000" />
Displays like this: Figure 7
<g:STextBox x:Name="Test4" Width="150" Height="25"
Margin="5" FontSize="15" Text="8001234567" Mask="(000) 000-0000" />
Figure 8
In addition, you can supply a Using a Mask or Format Library (and International Features)While a <g:STextBox x:Name="Zip" Width="150" Height="25" Margin="5" FontSize="15"
MaskLib="ZIP" Value="123457890" MaxLength="11" BorderThickness="0"/>
Figure 9
public static CultureInfo TextBoxCulture = CultureInfo.CurrentUICulture;
If for demonstration we change that to Figure 10
Voila, we get to see a Dutch (NL for Netherlands) formatted ZIP code (4 numbers and 2 letters). The code that makes this happen is in ControlHelpers.cs: private static string MaskZIP(string country)
{
switch (country)
{
case "nl": return "0000-LL";
default: return "00000-9999"; // USA
}
}
Of course my library of Validation of Data and Error HandlingAll that is left is validation of the data. It is okay for a For now, this is a short list (notice how public enum Validate: byte
{
None,
EmptyString,
Email,
Password,
SmallMoney
}
So we could provide the following XAML: <g:STextBox x:Name="Zip" Width="150" Height="25" Margin="5" FontSize="15"
MaskLib="ZIP" Value="123457890" MaxLength="11" BorderThickness="0"
Validate="EmptyString"/>
Now typing a blank value results in: Figure 11
Hovering over the “error” icon will popup a tooltip explaining that input for this field is required. In addition, there is an public delegate void ErrorConditionEventHandler(string condition);
public event ErrorConditionEventHandler ErrorCondition;
The event can be useful to let a form know there is an error (and for instance disable the “Save” button). History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||