Monday, February 21, 2011

Writing custom properties: #3 Edit control

In this post I will present how to create initial version of edit control for Guid property, which was introduced in previous posts.

Features
Created control will have:
  • Text input for Guid
  • Button for creating new Guid
This post is first step in more complex implementation which will come with AJAX functionality and On-Page-Edit support.

Controls render types
EPiServer properties can be rendered in one of the following modes (defined by EPiServer.Core.RenderType):
  • Default – when viewing property in View Mode. It’s used e.g. when you display property using <episerver:property PropertyName=”..”/> . In this mode CreateDefaultControls method is invoked.
  • Edit – when editing page in Edit Mode or in Quick Edit Mode. In this mode CreateEditControls method is invoked.
  • OnPageEdit – if PropertyDataControl.SupportsOnPageEdit returns true and <episerver:property PropertyName=”..”/> is used  property is rendered in this mode. Otherwise uses default view. These mode will be covered in more details soon.

Basic server control implementation
All controls for editing custom properties in EPiServer must inherit from PropertyDataControl or derived class.

In simplest scenario when supporting only edit mode functionally CreateEditControls must create controls which are used in edit mode. Edited values should be saved in ApplyEditChanges method.
Additionaly SetupEditControls updates visual representation value based on data from PropertyData
public class GuidPropertyControl : PropertyDataControl
{
    protected TextBox EditBox { get; set; }

    public GuidProperty GuidProperty
    {
        get { return (GuidProperty)this.PropertyData; }
    }

    public override void CreateEditControls()
    {
        //create text box
        this.EditBox = new TextBox { MaxLength = 0xff };
        this.ApplyControlAttributes(this.EditBox);
        this.Controls.Add(this.EditBox);

        //button for generating new guid
        //consider using LanguageManager for texts
        var button = new Button { Text = "New" };
        this.ApplyControlAttributes(button);
        button.Click +=  
              (sender, e) => { this.EditBox.Text = FormatGuid(Guid.NewGuid()); };
        this.Controls.Add(button);

        this.SetupEditControls();
    }


    private static string FormatGuid(Guid guid)
    {
        return guid.ToString();
    }

    //Saves value in underlying PropertyData 
    public override void ApplyEditChanges()
    {
        Guid value;
        if (string.IsNullOrEmpty(this.EditBox.Text))
        {
            value = Guid.Empty;
        }
        else
        {
            try
            {
                value = new Guid(this.EditBox.Text);
            }
            catch (FormatException e)
            {
                this.AddErrorValidator(e.Message);
                return;
            }
        }
        this.SetValue(value);
    }

    protected override void SetupEditControls()
    {
        this.EditBox.Text = FormatGuid(this.GuidProperty.Guid);
    } 
    //representation in View Mode
    public override  void CreateDefaultControls()
    {
        Label target = new Label();
        target.Text = FormatGuid(GuidProperty.Guid);
        this.CopyWebAttributes(target);
        this.Controls.Add(target);
    }

}


Post back support
If you would try to click “New button” in edit mode you would notice strange behavior: Message box warning about leaving page and loosing unsaved value.



The reason for this is that Javascipt OnBeforeUnload event is raised and EPiServer warns you that pages is going to be left with possible data loss.
There are two solutions for this:
  • Use ToolButton from EPiServer namespace instead of regular ASP Button and set DisablePageLeaveCheck to true

public override void CreateEditControls()
{
    //same code creating TextBox


    var button = new ToolButton
                        {
                            Text = "New", 
                            DisablePageLeaveCheck = true
                        };

   //same code using button
   
    SetupEditControls()
}

  • Use ScriptDisablePageLeaveEvent to disable checking specific event raised from our control. This approach is more flexible because not only button is supported and it’s possible to specify which event should suppress checking.

public override void CreateEditControls()
{
    //same code creating TextBox

    //same code creating Button

    ScriptDisablePageLeaveEvent scriptDisablePageLeaveEvent =
            new ScriptDisablePageLeaveEvent
            {
                EventTarget = button,
                EventType = EventType.Click
            };
        this.Controls.Add(scriptDisablePageLeaveEvent);
   
    SetupEditControls()
}

Page leave check considerations
You may wonder how it happed that without writing any code EPiServer can discover that value of Guid property has changed and warn about it. In fact EPiServer PageLeaveCheck attach to each html’s input event onchange and whenever this event is raise IsPageChanged is set to true. No magic!

This approach is working really well in most scenarios, since it’s common to store value in text boxes (sometimes in read-only mode like for Page Reference property). However, if you go into scenario where values are stored only in hidden field and displayed to user directly (without textbox) you will notice that Page Leave Check does not work anymore.

In that case you may manually mark page as changed using following Javascript

savePropetyValue: function (value) {
    //change hidden field value
    $("#hidden_field").val("...") ;

    //mark page as changed
    if (EPi.PageLeaveCheck.enabled) {
        EPi.PageLeaveCheck.SetPageChanged(true);
    }
};

Please be patient: more advanced Javascript usage in custom controls will be covered in future posts. It will include using JQuery, packaging javacripts, AJAX support, etc.

Sunday, February 13, 2011

Writing custom properties: #2 Default value

In previous post I presented how to create simple property returning Guid values. In this post I will briefly describe how to deal with default values for custom properties.
  1. Writing custom properties: #1 Custom value type
  2. Writing custom properties: #2 Default value 
  3. Writing custom properties: #3 Edit control

Admin UI based approach
The easiest way is to use admin UI and set default values for properties using three available modes:


Unfortunately this approach has several drawbacks:
  • Error-prone: It’s possible that for some reason administrator will not set this value. Application logic may not be prepared for this case.
  • Default value is applied only when creating page:  If you add new property to page type, value will not be updated for empty properties.
  • Editor can remove default value so property will fall back to empty/null value
  • String representation: It may be not easy/possible to specify string representation

In my opinion this value should be used as suggestion for editor, not as way to enforce data correctness.

Ensuring default value in custom property
Second approach for defining default value is setting it inside custom property.

There is method PropertyData.SetDefaultValue(), which is called when property is created (even if is null) or when cleared.

Together with SetDefaultValue, you will usually need implement IsNull property returning value indicating if current property value is equal to it's default.

Sample implementation for GuidPropety may be following:
[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{ 
    // for rest of code check previous post
    //.....

    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();

        this.value = Guid.Empty;
        //or set custom default value – default product code in store etc.
        //this.value = new Guid(“12345678-1234-1234-1234-123456789012”);
    }

    public override bool IsNull
    {
        get
        {
           return this.value == Guid.Empty;
           //return this.value == new Guid(“12345678-1234-1234-1234-123456789012”);
        }
    }
}


Some readers may ask why setting guid field to Guid.Empty is necessary, when Sytem.Guid is value type and it’s default value is Guid.Empty. Answer is that SetDefaultValue is called when property is cleared with PropertyData.Clear() so reseting to default value is necessary.

Usage
For newly created page or property added to exiting page

var value = CurrentPage["Guid"]; // “00000000-0000-0000-0000-000000000000”  
                               // or e.g “12345678-1234-1234-1234-123456789012”

var isNull = CurrentPage.Property["Guid"].IsNull; // returns true


Collection based custom properties
For collection types Microsoft design guidelines state:
Array (and collection) properties should never return a null reference. Null can be difficult to understand in this context.
http://msdn.microsoft.com/en-us/library/k2604h5s.aspx

To follow this guideline using SetDefaultValue is invaluable. Having custom property GuidCollectionProperty it can be implemented like below:

[PageDefinitionTypePlugIn] 
public class GuidCollectionProperty : PropertyString
{
    // for rest of code check previous post
    //.....
    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();
        this.value = new List<Guid>();
    }

    public override bool IsNull
    {
        get
        {
            return this.value == null || this.value.Count == 0;
        }
    }
}

Note: Admin default value override
It’s still possible to set default value using admin UI to override default value set by property creator. Good thing is that default value format will be verified, so it is not allowed to set “abc” as default value for GuidProperty.

Note: Virtual call in constructor
Current implementation of PropertyData base class calls virtual method SetDefaultValue() in constructor. This is discouraged because calling virtual method in base class can cause accessing uninitialized values in derived class, since derived constructor would not be invoked by that time.
Do not call virtual members from constructors. Calling virtual methods during construction will cause the most derived override to be called, even though the most derived constructor has not been fully run yet
http://msdn.microsoft.com/en-us/library/ms182331(v=vs.80).aspx
http://blogs.msdn.com/b/brada/archive/2004/08/12/213951.aspx

What does it mean for us? In order to preserve backward compatibility this behavior, I guess, won’t be fixed soon. Therefore we should be aware what we use in SetDafaultValue() and check if it has been initialized in constructor before accessing value.

Monday, February 7, 2011

Writing custom properties: #1 Custom value type

This is first post from series showing how to implement custom property in EPiServer in "proper" way without unnecessary hacking.
  1. Writing custom properties: #1 Custom value type
  2. Writing custom properties: #2 Default value
  3. Writing custom properties: #3 Edit control

Introduction
In EPiServer all custom properties must inherit from abstract base class PropertyData.  It’s value can be stored using one of following underlying types:
•    Boolean
•    Category
•    Date
•    FloatNumber
•    LinkCollection
•    LongString
•    Number
•    PageReference
•    PageType
•    String

Those types are defined in enumeration EPiServer.Core.PropertyDataType and when creating custom property you have to decide which storage type is closest to your requirements. From my experience you will usually use number or variations of string storage. The easiest way to start is to use one of default property type defined in EPiServer as your base class.

Guid Property
In this tutorial series I will show how to create complete custom property used for storing and editing Guid value. Using Guid as example will be easy to understand but still possible to show many features available for custom properties.

As usage example you may consider integrating with external system where product identifiers are stored as Guids and editor is responsible for providing them to our CMS.

Analysis

Guid are easily stored in string using different formats (the most popular is “{96C7DFDF-668C-4A71-AA79-1E8898B1A0E4}”), therefore as base type I will use PropertyString – originally used for storing up to 255 characters.

If we would leave property unchanged, editor could easily mistype value, which may cause system failure when application will be parsing invalid string. Creating custom property can prevent human error.

It’s also crucial to return proper object type, so developer can avoid parsing and, what is even worse, invalid format error handling in every place value is used.

//bad – requires parsing and error handling
Guid value=new Guid(CurrentPage[“GuidProperty”].Value); 

//good – returned value is System.Guid
Guid value= (Guid) CurrentPage[“GuidProperty”].Value; 

 Implementation

[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{
    private Guid value;

    public override Type PropertyValueType
    {
        get { return typeof(Guid); }
    }

    public override object Value
    {
        get { return this.value; }
        set
        {
            this.SetPropertyValue(value, delegate
            {
                //currenty we don't have custom editor control for guid, 
                //therefore if standard string control value sets value from editor
                //we try to parse it. Throwing exception in this place will show nice
                //message that provided value "is not valid value for property 
                //'YourPropertyName'"
                this.value = value is string? new Guid((string)value) : (Guid)value;

                if (Equals(value, Guid.Empty))
                    this.Clear();
                else
                    this.Modified();
            });
        }
    }

    public override void LoadData(object newValue)
    {
        this.Value = this.DeserializeValue(newValue as string);
    }

    public override object SaveData(PropertyDataCollection properties)
    {
        return this.SerializeValue(this.value);
    }

    protected string SerializeValue(Guid value)
    {
        return value.ToString();
    }
    protected Guid DeserializeValue(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return Guid.Empty;
        }        
        return new Guid(value);        
    }
}

 Usage

var value = CurrentPage["Guid"]; // e.g. c75dfcdb-258c-47a0-a2ea-81f0b92341e4
var type = CurrentPage["Guid"].GetType(); //System.Guid (not System.String!)

Friday, February 4, 2011

Dynamic splash screen in WPF

In WPF 3.5 SP1 new splash screen functionality was introduced. It’s now possible to add static image which is shown before main window is loaded.

Read more on MSDN: http://msdn.microsoft.com/en-us/library/cc656886.aspx

Unfortunately current implementation has some limitations. Since only static images are supported (BMP, GIF, JPEG, PNG, or TIFF format):
  • It’s not possible to use XAML to define splash screen design
  • It’s not possible to display dynamic informations like:
    • List of available plugins
    • Licensee information
    • Plugin being currently loaded. 

Solution overview
Solution for those issues is:
  1. Show built-in WPF splash screen
  2. Load regular WPF window (dynamic splash screen) which is pixel-perfect same as previously displayed splash screen but displays also dynamic information
  3. When all plugins and main window is loaded hide dynamic splash screen and show main window.


Implementation : DynamicSplashScreen class
DynamicSplashScreen is reusable class which  can be used as base class for your custom splash screen. Check inline comments for details.

public class DynamicSplashScreen:Window
{
    public DynamicSplashScreen()
    {
        //Defaults for splash screen
        this.ShowInTaskbar = false;
        this.WindowStartupLocation = WindowStartupLocation.Manual;
        this.ResizeMode = ResizeMode.NoResize;
        this.WindowStyle = WindowStyle.None;
        this.Topmost = true;

        this.Loaded += OnLoaded;
    }        

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        //calculate it manually since CenterScreen substracts  
        //taskbar's height from available area
        this.Left = (SystemParameters.PrimaryScreenWidth - this.Width) / 2;
        this.Top = (SystemParameters.PrimaryScreenHeight - this.Height) / 2;
    }
}

Creating pixel-perfect static splash screen image
Creating pixel-perfect static image can be error prone, maintenance hell if you start using external applications like Photoshop – each changed in dynamic version would have to be reflected in static image manually.

DynamicSplashScreen has another feature: capturing static splash screen image. It used build-in WPF functionality to get Control’s  “screen shot” dynamically.

You can use this method to generate image and add it to Visual Studio project with build action set to SplashScreen. This still requires some manual work, but it’s not possible (and reasonably) to generate splash screen at runtime.
public class DynamicSplashScreen:Window
{
    //.... see implementation above ..

    public void Capture(string filePath)
    {
        this.Capture(filePath, new PngBitmapEncoder());
    }

    public void Capture(string filePath, BitmapEncoder encoder)
    {
        RenderTargetBitmap bmp = new RenderTargetBitmap( 
                 (int)this.Width, (int)this.Height,  
                 96, 96, PixelFormats.Pbgra32);
        bmp.Render(this);


        encoder.Frames.Add(BitmapFrame.Create(bmp));

        using (Stream stm = File.Create(filePath))
        {
            encoder.Save(stm);
        }
    }
}

Usage
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // MyDynamicSplashScreen derives from DynamicSplashScreen and
        // defines layout and custom properties (like Licensee, Plugins, Message)
        MyDynamicSplashScreen splashScreen = new MyDynamicSplashScreen();
        splashScreen.Show();

        //set basic dynamic data on splash screen
        splashScreen.AvailablePlugins = new[] {"Plugin 1", "Plugin 2"};
        splashScreen.Licensee = "Mirosław Jedynak";

        //use during development to generate image and embed it in application
        //splashScreen.Capture(@"c:\StaticSplashScreen.png");

        var startupTask=new Task(() =>
            {
                //Load plugins in non-UI thread - may be time consuming
                for (int i = 0; i < 3; i++)
                {
                    //set custom message on screen
                    splashScreen.Dispatcher.BeginInvoke(
                        (Action) (()=>splashScreen.Message = "Loading:  Plugin " + i));
                            
                    Thread.Sleep(100);
                }
            });

        //when plugin loading finished, show main window
        startupTask.ContinueWith(t =>
            {
                MainWindow mainWindow = new MainWindow();

                //when main windows is loaded close splash screen
                mainWindow.Loaded += (sender, args) => splashScreen.Close();

                //set application main window;
                this.MainWindow = mainWindow;

                //and finally show it
                mainWindow.Show();
            }, TaskScheduler.FromCurrentSynchronizationContext());

        startupTask.Start();
    }       
}


Sample Dynamic splash screen
<Startup:DynamicSplashScreen x:Class="DynamicSplashScreenDemo.Startup.SplashScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:Startup="clr-namespace:DynamicSplashScreenDemo.Startup"  
        Title="My application"   x:Name="spashScreen"  
        Width="500" Height="300">
    <Border BorderThickness="1" BorderBrush="Black">
        <Grid >
            <Rectangle>
                <Rectangle.Fill>
                    <LinearGradientBrush EndPoint="0,0" StartPoint="0.5,1.3">
                        <GradientStop Color="#FF07254E" Offset="1"/>
                        <GradientStop Color="White" Offset="0.369"/>
                        <GradientStop Color="White"/>
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
            
            <TextBlock  FontFamily="Verdana" FontWeight="Bold" FontSize="50"  
                VerticalAlignment="Top" HorizontalAlignment="Center"  
                TextAlignment="Center"  Margin="0,40,0,0" >
                <Run Text="Dynamic" Foreground="#FF006AB3" />
                <LineBreak/>
                <Run Text="splash screen" Foreground="#FF006AB3"/>
            </TextBlock>

            <TextBlock Text="{Binding ElementName=spashScreen, Path=Message}"  
             VerticalAlignment="Bottom"  Margin="10"/>
            <StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"  
                 Margin="10">
                <ItemsControl  Margin="0,0,0,10" HorizontalAlignment="Right"
                ItemsSource="{Binding ElementName=spashScreen, Path=AvailablePlugins}"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Licensed to: "/>
                    <TextBlock Text="{Binding ElementName=spashScreen, Path=Licensee}"/>
                </StackPanel>
            </StackPanel>
        </Grid>
    </Border>
</Startup:DynamicSplashScreen>

Source: DynamicSplashScreenDemo_source.zip

Shout it
kick it on DotNetKicks.com