3
votes

I'm learning Silverlight using Silverlight 2.0 Unleashed + Silverlight 4.0 Unleashed and, well, just messing around with it :-)

As part of that, I'm trying to develop what should be a very simple content control: A label that you can edit by clicking on it.

I took my inspiration from another SO question (Click-to-edit in Silverlight) but I'm having a few issues related to me being very new to Silverlight. :)

I'll first post the code, then my questions.

This is my XAML:

<ContentControl x:Class="MyTestProject.SilverlightControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" xmlns:local="clr-namespace:MyTestProject" mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <ContentControl.Resources>
        <ControlTemplate x:Key="EditableLabelTextboxTemplate">
            <TextBox Name="UCTB" Text="{Binding Text, Mode=TwoWay}" Width="100" Height="25" ></TextBox>
        </ControlTemplate>
        <ControlTemplate x:Name="LabelTemplate" TargetType="local:SilverlightControl1">
            <sdk:Label x:Name="UCLBL" Content="{Binding Text}" />
        </ControlTemplate>
        <Style TargetType="local:SilverlightControl1">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate x:Name="LabelTemplate2" TargetType="local:SilverlightControl1">
                        <sdk:Label x:Name="UCLBL" Content="{Binding Text}" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ContentControl.Resources>
</ContentControl>

and this is my CodeBehind:

public partial class SilverlightControl1 : ContentControl
    {
        public string Text { get; set; }

        public SilverlightControl1()
        {
            this.DataContext = this;
            Text = "Hello, World!";
            this.DefaultStyleKey = typeof(SilverlightControl1);
            //Template = this.Resources["LabelTemplate"] as ControlTemplate;
            InitializeComponent();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.MouseLeftButtonDown += HandleLabelLeftMouseDown;
            this.KeyDown += HandleTextboxEnter;
        }

        public static void HandleTextboxEnter(object sender, KeyEventArgs args)
        {
            var ctrl = sender as SilverlightControl1;
            if (ctrl.Template == ctrl.Resources["EditableLabelTextboxTemplate"])
            {
                if (args.Key == Key.Enter)
                {
                    ctrl.Template = ctrl.Resources["LabelTemplate"] as ControlTemplate;
                }
            }
        }

        public static void HandleLabelLeftMouseDown(object sender, MouseButtonEventArgs args)
        {
            var editableLabel = sender as SilverlightControl1;
            if (editableLabel.Template != editableLabel.Resources["EditableLabelTextboxTemplate"])
            {
                editableLabel.Template = editableLabel.Resources["EditableLabelTextboxTemplate"] as ControlTemplate;
            }
        }
    }

First of all, the way I have to instantiate my Control is very clumsy and redundant.

There are 2 ControlTemplates (one label, one textbox) BUT there's also a Style with a label for initializing. If I remove this (and the correspoding line in the constructor in the codebehind), nothing renders, even if I set the template as well. There must be a better way?

My second problem is that when I then place my control in a test project and uses it, the control moves? It'll start off at the left-hand side in the middle as a label, I then click it, and the textbox appears centered on the page, and on enter the label reappears on the left-hand side of the screen?

EDIT: Thirdly, once I replace the label with the textbox, the textbox doesn't have focus, and I can't figure out how to give it to it?

Lastly, right now the only way to stop editing is to hit "enter". Ideally, I'd like to just click outside the textbox, but using the lost focus event (as suggested in the link) seems to fire way too often for me to be able to use it?

I hope some of the Silverlight guru's out there can help me! :-)

Oh, and should someone know of a click-to-edit Control available I could use / look at, I'd be very grateful too :)

Thanks in advance!

1

1 Answers

3
votes

I hope I get you right. What you need is a TextBox which is not editable at the beginning, but if you click on it, it will be editable.

For this I created a control which inherits from a TextBox (I used the vs template TemplatedControl). I added two states (Edit and NotEdit) and a Rectangle which acts as layer over the text for the not edit mode (to prevent to change the mouse cursor on mouse over). I also did some assignments on the borders. E.g. in the not edit states, the borders are size 0, so that the TextBox looks like a Label.

Here's the xaml:

<Style TargetType="local:ClickToEditTextBox">
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Background" Value="#FFFFFFFF"/>
            <Setter Property="Foreground" Value="#FF000000"/>
            <Setter Property="Padding" Value="2"/>
            <Setter Property="BorderBrush">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FFA3AEB9" Offset="0"/>
                        <GradientStop Color="#FF8399A9" Offset="0.375"/>
                        <GradientStop Color="#FF718597" Offset="0.375"/>
                        <GradientStop Color="#FF617584" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:ClickToEditTextBox">
                        <Grid x:Name="RootElement">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="MouseOver"/>
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="ReadOnly">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="ReadOnlyVisualElement" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="FocusStates">
                                    <VisualState x:Name="Focused">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Unfocused">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="0" Duration="0"/>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="ValidationStates">
                                    <VisualState x:Name="Valid"/>
                                    <VisualState x:Name="InvalidUnfocused">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Visible</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="InvalidFocused">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Visible</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <sys:Boolean>True</sys:Boolean>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <!-- The new edit states -->
                                <VisualStateGroup x:Name="EditStates">
                                    <VisualState x:Name="Edit">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="rectangle">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Collapsed</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="NotEdit">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="Border">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Thickness>0</Thickness>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border x:Name="Border" CornerRadius="1" Opacity="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1">
                                <Grid>
                                    <Border x:Name="ReadOnlyVisualElement" Opacity="0" Background="#5EC9C9C9"/>
                                    <Border x:Name="MouseOverBorder" BorderThickness="1" BorderBrush="Transparent">
                                        <Grid>
                                            <ScrollViewer x:Name="ContentElement" Padding="{TemplateBinding Padding}" BorderThickness="0" IsTabStop="False"/>
                                            <Rectangle x:Name="rectangle" Fill="#02FFFFFF" />
                                        </Grid>
                                    </Border>
                                </Grid>
                            </Border>
                            <Border x:Name="DisabledVisualElement" Background="#A5F7F7F7" BorderBrush="#A5F7F7F7" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0" IsHitTestVisible="False"/>
                            <Border x:Name="FocusVisualElement" BorderBrush="#FF6DBDD1" BorderThickness="{TemplateBinding BorderThickness}" Margin="1" Opacity="0" IsHitTestVisible="False"/>
                            <Border x:Name="ValidationErrorElement" BorderThickness="1" CornerRadius="1" BorderBrush="#FFDB000C" Visibility="Collapsed">
                                <ToolTipService.ToolTip>
                                    <ToolTip x:Name="validationTooltip" Template="{StaticResource ValidationToolTipTemplate}" Placement="Right" 
                         PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
                         DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                                        <ToolTip.Triggers>
                                            <EventTrigger RoutedEvent="Canvas.Loaded">
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
                                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                                <DiscreteObjectKeyFrame.Value>
                                                                    <sys:Boolean>true</sys:Boolean>
                                                                </DiscreteObjectKeyFrame.Value>
                                                            </DiscreteObjectKeyFrame>
                                                        </ObjectAnimationUsingKeyFrames>
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </EventTrigger>
                                        </ToolTip.Triggers>
                                    </ToolTip>
                                </ToolTipService.ToolTip>
                                <Grid Width="12" Height="12" HorizontalAlignment="Right" Margin="1,-4,-4,0" VerticalAlignment="Top" Background="Transparent">
                                    <Path Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C"/>
                                    <Path Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff"/>
                                </Grid>
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

As you can see, there are two new states, Edit and NotEdit.

So on code behind I "send" the control initialy to the NotEdit state and add two handlers, for GotFocus and LostFocus.

this.GotFocus += ClickToEditTextBox_GotFocus;
this.LostFocus += ClickToEditTextBox_LostFocus;
VisualStateManager.GoToState(this, "NotEdit", false);

The handlers just "send" the control in the defined state:

void ClickToEditTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "NotEdit", false);
        }

        void ClickToEditTextBox_GotFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "Edit", false);
        }

And here's the full src code:

public class ClickToEditTextBox : TextBox
    {
        public ClickToEditTextBox()
        {
            this.DefaultStyleKey = typeof(ClickToEditTextBox);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.GotFocus += ClickToEditTextBox_GotFocus;
            this.LostFocus += ClickToEditTextBox_LostFocus;

            VisualStateManager.GoToState(this, "NotEdit", false);

        }

        void ClickToEditTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "NotEdit", false);
        }

        void ClickToEditTextBox_GotFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "Edit", false);
        }
    }

And now you can use it in your UserControl:

<SilverlightApplication1:ClickToEditTextBox Text="12345" Width="100" Height="22" />

This is just a quick implementation, so some improvements can be made. But I hope, this helps you with your problem.

BR, TJ