in
Forums
Blogs
DevExpress.com
Client Center
Support Center
DevExpress Channel

The One With

  • Enabling Collaboration with RichEdit Control

    The cool thing about the XtraRichEdit (WinForms) and AgRichEdit (Silverlight) controls is that they share the same core. Everything that is available in one, is also available in another. Excluding some platform limitations, of course, like clipboard access and support for printing (which is not available in Silverlight). So everything I talk about here applies to both the WinForms and Silverlight versions of the editor.

    Service Architecture (Under the hood)

    The Rich Edit control uses a service architecture underneath. Every single task that needs to be performed against it or anything that it needs from the “user land”, i.e. your application, goes through what we call a service layer. For example the IKeyboardHandlerService is responsible for, as name implies, everything do with keyboard, the IMouseHandlerService handles the mouse input etc. So what does this mean to us? Well… services are installable! So if I want to override some behavior I simply need to install a custom service.

    An example of this is IUriStreamService. IUriStreamService is invoked whenever the editor needs to load data from a specific URI. Say you are reading some HTML in and want to override how the editor fetches the dependency files like CSS:

    Create a new URI Service:

    public class MyUriStreamService : IUriStreamService {

        public System.IO.Stream GetStream(string url) {

            // just for fun return something bogus....

            return new FileStream("C:\\Windows\\win.ini",  FileMode.Open, FileAccess.Read);

        } 

        public void RegisterProvider(IUriStreamProvider provider) {}

        public void UnregisterProvider(IUriStreamProvider provider) {}

    }

    then simply install it into the DocumentModel.

    richEditControl.AddService(typeof(IUriStreamService), new MyUriStreamService());

    Everything is a Command

    Any action that is performed against the editor is translated into a command. For example the IKeyboardHandlerService catches the keyboard input, translates it into a specific command then sends it to the DocumentModel for processing. And everything is a command, Keyboard Input, Scrolling, Range Selection, Formatting etc… and with the help of a special ICommandExecutionListenerService service we can actually hook into the command queue and perform custom actions.

    You have probably seen the split screen feature in Visual Studio Code editor or WinWord. To do that with the XtraRichEdit and AgRichEdit controls, simply listen for the commands on one and send them to another. We can take this idea even further and replicate our documents over the network in real-time :) (think Adobe Buzzword or that cool chat feature of Google Wave)

    Listening To Commands

    To listen to the commands we can inherit from CommandExecutionListenerService and override EndCommandExecution method:

    public override void EndCommandExecution(DevExpress.Utils.Commands.Command command,

            DevExpress.Utils.Commands.ICommandUIState state) {

        base.EndCommandExecution(command, state);  

        if(!IsUpdateLocked) {

            if(command is ExtendSelectionByRangesCommandBase) {

                _queue.Enqueue(new SelectionCommand(_control, _control.Document.Selection));

            }

            else {

                _queue.Enqueue(command);

            }

            ThreadPool.QueueUserWorkItem(Flush, this);

        }

    }

    The IsUpdateLocked means that the specific command is part of another command (a sub command if you will) so we do not need to replicate it.

    Inside flush, we will serialize all the commands received so far and send them to some other rich edit control.

    public void Flush(object sender) {

        DevExpress.Utils.Commands.Command command;

        while(_queue.Dequeue(out command)) {

            try {

                string stringParam1 = string.Empty;

                int intParam1 = 0;

                int intParam2 = 0;

                if(command is InsertTextCommand) {

                    stringParam1 = ((InsertTextCommand)command).Text;

                }

                else if(command is ScrollVerticallyByLogicalOffsetCommand) {

                    intParam1 = ((ScrollVerticallyByLogicalOffsetCommand)command).LogicalOffset;

                }

                else if(command is SelectionCommand) {

                    intParam1 = ((SelectionCommand)command).Start;

                    intParam2 = ((SelectionCommand)command).Length;

                }

                PresenceClient.Default.SendCommand(command.GetType().AssemblyQualifiedName,

                    stringParam1,

                    intParam1,

                    intParam2);

            }

            catch(Exception e) {

                Context.Log(e);

            }

        }

    }

    Receiving Commands

    The most important part about receiving commands is to properly deserialize them. It does not matter what network stack you use to send and receive, as long as you handle the params properly and, in some cases, special case some of them (like scrolling).

    void DeserealizeCommand(DeserealizationInfo info) { 
        Command command = (Command)Activator.CreateInstance(info.ResolvedType, richEditControl.RichControl); 
        _listenerService.AddToIgnoreList(command); 
        command.CommandSourceType = (CommandSourceType)(-3224); 

        if(command is InsertTextCommand) {

            ((InsertTextCommand)command).Text = info.ProtocolCommand.StringParam1;

            command.Execute();

        }

        else if(command is SelectionCommand) {

            richEditControl.RichControl.Document.Selection =

                richEditControl.RichControl.Document.CreateRange(info.ProtocolCommand.IntParam1, info.ProtocolCommand.IntParam2);

        }

        else if(command is ScrollVerticallyByLogicalOffsetCommand) {

            ((ScrollVerticallyByLogicalOffsetCommand)command).LogicalOffset = info.ProtocolCommand.IntParam1;

            command.Execute();

        }

        else if(command is ScrollHorizontallyByPhysicalOffsetCommand) {

        }

        else if(command is ExtendSelectionByCharactersCommand) {

        }

        else {

            command.Execute();

        }

    }

    Download Source Code

    Cheers,

    Azret

  • Silverlight Menu Control: Customizing Appearance (v9.2)

    In my previous post I have explained how to customize the appearance of the AgMenu Control. The template for it needs to be updated a bit to properly work with the 9.2 build.

    List of changes:

    • AgMenuDefaultStyleHelper, AgSubMenuDefaultStyleHelper and the like have been moved to a separate namespace clr-namespace:DevExpress.AgMenu.DefaultStyle
    • AgMenuItemContentPresenter has been moved to clr-namespace:DevExpress.AgMenu.Internal
    • AgMenuItemContentPresenter.CheckShowing has been renamed to CheckAreaVisibility
    • AgMenuItemContentPresenter.VisualPosition has been renamed to Position

    To make it a bit more interesting, I made the template look like the Darkroom Skin

    and moved the XAML into a separate file so that it can be reused. To add it to your app, simply merge the AgMenu.xaml into your application resources.

    <Application.Resources>

    <ResourceDictionary>

                <ResourceDictionary.MergedDictionaries>

                    <ResourceDictionary Source="Assets/DevExpress/AgMenu.xaml"/>

                </ResourceDictionary.MergedDictionaries>

    </ResourceDictionary>

    </Application.Resources>

    and then apply it to your AgMenu

    <dxm:AgMenu Style="{StaticResource AgMenu;DarkRoom}">

        <dxm:AgMenuItem Header="File">

            <dxm:AgMenuItem Header="New">

                <dxm:AgMenuItem Header="Contact"></dxm:AgMenuItem>

                <dxm:AgMenuItem Header="Database"></dxm:AgMenuItem>

            </dxm:AgMenuItem>

            <dxm:AgMenuItem Header="Edit"></dxm:AgMenuItem>

            <dxm:AgMenuItem Header="Delete"></dxm:AgMenuItem>

        </dxm:AgMenuItem>

        <dxm:AgMenuItem Header="Help">

        </dxm:AgMenuItem>

    </dxm:AgMenu>

    Download the AgMenu 9.2 Darkroom Template (Right click: Save As…)

    Cheers,

    Azret

  • Silverlight Windows, Dialogs and more from AgCore

    Complex controls like AgDataGrid are made up of small little parts. Often this parts are generic enough and can be reused by the user code. We introduced AgCore in 9.1 as a shared library for all our controls. In 9.2 we are surfacing some of what’s inside.

    Windows and Dialogs

    A window can be created either in XAML or in code and can be displayed modeless or as modal dialog.

    xmlns:dxc="clr-namespace:DevExpress.AgCore;assembly=DevExpress.AgCore.v9.2"

    <dxc:AgWindow x:Name="window" Title="Title">

        <sys:String>Content</sys:String>

    </dxc:AgWindow>

     

    private void Button_Click(object sender, RoutedEventArgs e) {

        window.Show();

    }

    private void Button_Click(object sender, RoutedEventArgs e) {

        window.ShowDialog();

    }

    private void Button_Click(object sender, RoutedEventArgs e) {

        AgWindow window = new AgWindow() { Title = "Title", Content="Content" };

        window.Show();

    }

     

    When a window is shown as modal, a background veil (AgVeil) with no HitTest will be created under it, giving that modality effect.

    Dialogs and MessageBoxes

    <dxc:AgDialog x:Name="userNameDlg" Title="Enter Name" Width="300" HorizontalContentAlignment="Stretch">

        <TextBox dxc:AgStyleManager.IsStyled="True" x:Name="userName" Margin="20"/>

    </dxc:AgDialog>

     

    private void Button_Click(object sender, RoutedEventArgs e) {

        userName.Text = "[Your Name]";

        userNameDlg.ShowDialog(() => {

            // Do something with user input...

        });

    }

     

    or

     

     

    AgDialog dlg = new AgDialog() { Title = "Confirmation...", Content = "Delete current record?" };

    dlg.ShowDialog(() => {

        PerformDelete();

    });

    Sample data entry form:

     

     

    xmlns:dxl="clr-namespace:DevExpress.AgLayoutControl;assembly=DevExpress.AgLayoutControl.v9.2"

    xmlns:dxr="clr-namespace:DevExpress.XtraRichEdit;assembly=DevExpress.AgRichEdit.v9.2"

    <dxc:AgDialog x:Name="dataForm" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"

                  Width="840" Height="600" Title="New Contact" >

        <dxl:LayoutControl VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Orientation="Vertical">

            <dxl:LayoutGroup View="GroupBox" Orientation="Vertical" Header="Info">

                <dxl:LayoutItem Label="Full Name:" VerticalAlignment="Top">

                    <dxe:TextEdit x:Name="fullNameEdit" EditValue="{Binding FullName}"></dxe:TextEdit>

                </dxl:LayoutItem>

                <dxl:LayoutItem Label="Home Phone:" VerticalAlignment="Top">

                    <dxe:TextEdit x:Name="homePhoneEdit"  EditValue="{Binding HomePhone}">

                        <dxe:TextEdit.Mask>

                            <dxe:MaskProperties MaskType="Simple" EditMask="(999)000-00-00"/>

                        </dxe:TextEdit.Mask>

                    </dxe:TextEdit>

                </dxl:LayoutItem>

                <dxl:LayoutItem Label="Cell Phone:" VerticalAlignment="Top">

                    <dxe:TextEdit x:Name="cellPhoneEdit" EditValue="{Binding CellPhone}"></dxe:TextEdit>

                </dxl:LayoutItem>

                <dxl:LayoutItem Label="DOB:" VerticalAlignment="Top">

                    <dxe:DateEdit x:Name="dobEdit"></dxe:DateEdit>

                </dxl:LayoutItem>

                <dxl:LayoutItem Label="Group:" VerticalAlignment="Top">

                    <dxe:ComboBoxEdit x:Name="groupEditEdit" EditValue="{Binding CellPhone}">

                        <dxe:ComboBoxEdit.Items>

                            <sys:String>Family</sys:String>

                            <sys:String>Friends</sys:String>

                            <sys:String>Co-Workers</sys:String>

                        </dxe:ComboBoxEdit.Items>

                    </dxe:ComboBoxEdit>

                </dxl:LayoutItem>

            </dxl:LayoutGroup>

            <dxl:LayoutGroup View="GroupBox" Header="Notes" VerticalAlignment="Stretch">

                <dxre:RichEdit x:Name="richEdit" ActiveViewType="Draft">

                </dxre:RichEdit>

            </dxl:LayoutGroup>

        </dxl:LayoutControl>

    </dxc:AgDialog>

    Applying the look and feel

    To help you style standard controls too look and feel like the controls from DevExpress (AgDataGrid, AgMenu, AgRichEdit etc…) we have introduced a new helper class AgSyleManager. All you have to do is choose the look and feel (Visual Style)

    Buttons

     

    <Button Content="Standard Button"></Button>

    <Button Content="DX Button" dxc:AgStyleManager.StyleType="Button"></Button>

    <Button Content="DX ToolButton" dxc:AgStyleManager.StyleType="ToolButton"></Button>

    In most cases it is enough to just set the AgStyleManager.IsStyled property. The AgStyleManager will then discover the control type (if supported) and apply the default DevExpress template.

     <Button Content="DX Button" dxc:AgStyleManager.IsStyled="true">

    ListBox

    image

    <ListBox dxc:AgStyleManager.StyleType="ListBox"></ListBox>

    ComboBox

     

    <ComboBox dxc:AgStyleManager.StyleType="ComboBox"/>

    CheckBox

    image

    <CheckBox Content="Standard CheckBox"/>

    <CheckBox dxc:AgStyleManager.IsStyled="True" Content="DX CheckBox"/>

     

     

    TextBox

     

    \

     

    <TextBox Text="Standard TextBox"/>

    <TextBox dxc:AgStyleManager.StyleType="TextBox" Text="DX TextBox"/>

    Slider

     

    <Slider Width="150"/>

    <Slider dxc:AgStyleManager.StyleType="Slider"/>

     

    ScrollBar

     

     

    <ScrollBar Orientation="Horizontal"/>

    <ScrollBar dxc:AgStyleManager.StyleType="ScrollBar" Orientation="Horizontal"/>

    Cheers,

    Azret

  • Silverlight Data Editors and Mask Edits in 9.2

    The 9.2 release introduces a lot of new controls and features to our Silverlight suite. In this release we are formally introducing the concept of editors, both as standalone and as embeddable. A lot of work has been done to merge our exiting in-place edits from the AgDataGrid and our editors from WPF to create a sound framework from which we can derive things like TextEdits, DateEdits, ComboBoxes etc.

    BaseEdit

    An abstract control from which all other editors derive. BaseEdit provides

      • the basic look and feel
      • an abstraction for Display and Edit templates (recall the ColumnType templates in the AgDataGrid, but now at a much lower level)
      • support for *validation* and an ErrorPresenter

    * BaseEdit cannot will be used directly. Use it only to create a custom and specialized editors.

    CheckEdit

    image 

    <dxe:CheckEdit></dxe:CheckEdit>

    DateEdit

    image

    <dxe:DateEdit></dxe:DateEdit>

    ComboEdit

    image 

    <dxe:ComboBoxEdit>

        <dxe:ComboBoxEdit.Items>

            <sys:String>Item 1</sys:String>

            <sys:String>Item 2</sys:String>

            <sys:String>Item 3</sys:String>

            <sys:String>Item 4</sys:String>

            <sys:String>Item 5</sys:String>

        </dxe:ComboBoxEdit.Items>           

    </dxe:ComboBoxEdit>

     

    ButtonEdit

    image

    <dxe:ButtonEdit></dxe:ButtonEdit>

    <dxe:ButtonEdit>

        <dxe:ButtonEdit.Buttons>

            <dxe:ButtonInfo GlyphKind="Regular" IsLeft="True"/>

        </dxe:ButtonEdit.Buttons>

    </dxe:ButtonEdit>

    TextEdit

    image 

    <dxe:TextEdit></dxe:TextEdit>

    <dxe:TextEdit>

        <dxe:TextEdit.Mask>

            <dxe:MaskProperties MaskType="DateTime"/>

        </dxe:TextEdit.Mask>

    </dxe:TextEdit>

     

    <dxe:TextEdit>

        <dxe:TextEdit.Mask>

            <dxe:MaskProperties MaskType="Simple" EditMask="(999)000-0000"/>

        </dxe:TextEdit.Mask>

    </dxe:TextEdit>

    TextEdit and it’s descendants (All ButtonEdit, ComboEdit etc..) supports Masking. For for editing and displaying purposes. Supported masks range from the very simple

    DateTime and Numeric to the very complex powered by a Regular Expression of your choosing.

    Simple Masks
    image 

    <dxe:MaskProperties MaskType="Simple" EditMask="(999)000-00-00"/> // US Phone Number

    <dxe:MaskProperties MaskType="Simple" EditMask="99/99/00"/> // Short Date

    <dxe:MaskProperties MaskType="Simple" EditMask="90:00:00 &gt;LL"/> // Long Time

    <dxe:MaskProperties MaskType="Simple" EditMask="90:00"/> // Short Time

    <dxe:MaskProperties MaskType="Simple" EditMask="999"/> // 3 Digit Phone Ext.

    <dxe:MaskProperties MaskType="Simple" EditMask="000-00-0000"/> // Social Security Number

    <dxe:MaskProperties MaskType="Simple" EditMask="00000-9999"/> // Long Zip Code

    <dxe:MaskProperties MaskType="Simple" EditMask="00000"/> // Short Zip Code

    Numeric Masks

    image

    <dxe:MaskProperties MaskType="Numeric" EditMask="c"/> // Currency

    <dxe:MaskProperties MaskType="Numeric" EditMask="#,##0.00;&lt;&lt;#,##0.00&gt;&gt;"/> // Negative Number

    <dxe:MaskProperties MaskType="Numeric" EditMask="n"/> // Number

    <dxe:MaskProperties MaskType="Numeric" EditMask="d8"/> // 8 digits

    <dxe:MaskProperties MaskType="Numeric" EditMask="p"/> // Percent (with decimals)

    <dxe:MaskProperties MaskType="Numeric" EditMask="P"/> // Percent

    Date and Time Masks
    image 

    <dxe:MaskProperties MaskType="DateTime" EditMask="f"/> // Full

    <dxe:MaskProperties MaskType="DateTime" EditMask="MM/dd/yyyy h:m:s t"/> // Custom

    <dxe:MaskProperties MaskType="DateTime" EditMask="R"/> // RFC 1123

    <dxe:MaskProperties MaskType="DateTime" EditMask="u"/> // Universal

    <dxe:MaskProperties MaskType="DateTime" EditMask="M"/>  // Month and Day

    <dxe:MaskProperties MaskType="DateTime" EditMask="Year: yyyy"/> // Year

    <dxe:MaskProperties MaskType="DateTime" EditMask="t"/> // Time Only

    <dxe:MaskProperties MaskType="DateTime" EditMask="d, dddd"/> // Day and Day of the Week

    Regular Masks
    image 

    <dxe:MaskProperties MaskType="Regular" EditMask="(\d?\d?\d?)\d\d\d-\d\d-\d\d"/> // Phone Number

    <dxe:MaskProperties MaskType="Regular" EditMask="\d?\d?/\d\d/\d\d"/> // Short Date

    <dxe:MaskProperties MaskType="Regular" EditMask="\d?\d:\d\d:\d\d >[PA]M"/> // Long Time

    <dxe:MaskProperties MaskType="Regular" EditMask="\d?\d:\d\d"/> // Short Time

    RegEx Masks
    image

    <dxe:MaskProperties MaskType="RegEx" EditMask="((10|11|12|[1-9]):[0-5]\d:[0-5]\d(am|pm))|((2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9])"> // Time

    <dxe:MaskProperties MaskType="RegEx" EditMask="((\+\d|8)?\(\d{3}\))?\d{3}-\d\d-\d\d"/> // Phone Number

    <dxe:MaskProperties MaskType="RegEx" EditMask="([1-9]|10|11|12)/(0?[1-9]|[12]\d|30|31)/\d{2}\d{2}?"> // Short Date

    <dxe:MaskProperties MaskType="RegEx" EditMask="([01]?[0-9]|2[0-3]):[0-5]\d"/> // Short Time

    <dxe:MaskProperties MaskType="RegEx" EditMask="\d{3}-\d{2}-\d{4}"/> // Social Security

    TextEdit and AgDataGrid

    image

     

    <dxg:AgDataGrid>

        <dxg:AgDataGrid.Columns>

            <dxg:AgDataGridColumn FieldName="CellPhone">

                <dxg:AgDataGridColumn.EditSettings>

                    <dxes:TextEditSettings>

                        <dxes:TextEditSettings.Mask>

                            <dxe:MaskProperties MaskType="Simple" EditMask="(999)000-00-00"/>

                        </dxes:TextEditSettings.Mask>

                    </dxes:TextEditSettings>

                </dxg:AgDataGridColumn.EditSettings>

            </dxg:AgDataGridColumn>

        </dxg:AgDataGrid.Columns>

    </dxg:AgDataGrid>

    Cheers,

    Azret

  • Twitter Account

    So I have resisted the twitter for some time now. Not because it isn’t cool. Because it is. Or as someone at MIX’09 last week said to me.

    “Like, it’s like totally cool man, like dude why are you not on it?!?” Twitter__What_are_you_doing_-3.jpg

    Well, I had an account, but I have no idea what password I selected and or what email address I registered it to. Lame I know.

    Julian posted that some of us from DevExpress are already there. You can read it here.

    and I have just created an account, so join us.

    Azret Botash @dxlight “Did you know there is a DevExpress.Utils.Compress.Compressor?”

     

    See you on twitter

    Azret

  • iPhone like Touch Interface with Silverlight Grid

    In the recent year or two, touch interfaces (with the help of cool devices like iPhone) become more popular then ever. Even some of the new desktop machines are now touch enabled. If you have been to PDC2008, you might have seen those cool all in one HP TouchSmarts. They were showing them off during the keynote and at the DevExpress booth, had them for our own demos. In fact, ever since PDC, I have been using one exclusively instead of a laptop. (And yes, I carry it with me everywhere :)).

    For Windows 7, Microsoft is introducing a new set of APIs to deal with multi-touch and so does Silverlight 3. And as developers, we should start exploring this relatively new UI concepts and think about whether or not it is applicable to our own apps.

    In this article, we are going to explore the AgDataGrid from DXperience 2009 vol1 and how to customize it to look and feel just like an iPhone. In fact, we are going to do much more then that…we are going to build a complete “Soft-Phone” and IM experience using Silverlight 3, WinForms and the controls from DXperience 2009 vol1.

     

    image

    Pre-requisites

     

    First Steps

    Let’s start with the AgTouchGrid (our customized AgDataGrid) first and deal with the backend and architecture later.

    We will need reference the following assemblies:

    • DevExpress.AgCore.v9.1: This is where we keep the most common classes and utils and extensions methods.
    • DevExpress.AgData.v9.1: The place for all data related helpers. Most notably, this is where our DataController lives.
    • DevExpress.AgEditors.v9.1: A new assembly, and as the name implies, contains all the basic data editors and the plumbing to reuse them as standalone or inside the AgDataGrid+.
    • DevExpress.AgDataGrid.v9.1: The AgDataGrid and family.

    Inheriting from AgDataGrid:

    public class AgTouchGrid : DevExpress.AgDataGrid.AgDataGrid {
        public AgTouchGrid() {
            DefaultStyleKey = typeof(AgTouchGrid);            
        }
    }

    we’ll create a custom template for our new AgTouchGrid.

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DevExpress.AgDataGrid;assembly=DevExpress.AgDataGrid.v9.1"
        xmlns:touch="clr-namespace:DevExpress.Exchange.Communicator.Silverlight.Controls"
        xmlns:internal="clr-namespace:DevExpress.AgDataGrid.Internal;assembly=DevExpress.AgDataGrid.v9.1"
        xmlns:utils="clr-namespace:DevExpress.AgDataGrid.Utils;assembly=DevExpress.AgDataGrid.v9.1"
        xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"
        xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
        
        <!--AgTouchGrid-->
        <Style TargetType="touch:AgTouchGrid">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="touch:AgTouchGrid">
                        <Grid x:Name="Surface">
                            <Grid.Resources>
                                <Storyboard x:Name="TouchStory">
                                    <DoubleAnimation x:Name="Touch Animation" Duration="0:0:0.3000" 
                                                     Storyboard.TargetName="VerticalScrollbarElement" Storyboard.TargetProperty="Value"  />
                                </Storyboard>
                            </Grid.Resources>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"  />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid Name="RootElement" Background="White" Cursor="Hand">
                                <Grid.RowDefinitions>
                                    ....
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    ....
                                </Grid.ColumnDefinitions>
                                
                                .....
                                
                                <Grid Grid.Column="2" Grid.Row="6" Grid.RowSpan="1" Width="17" Visibility="Collapsed">
                                    <ScrollBar x:Name="VerticalScrollbarElement" Orientation="Vertical" />
                                </Grid>
                                
                                <ContentControl x:Name="CellsClipperElement" Background="White" Grid.Column="1" Grid.Row="6">
                                    <internal:AgLineStackPanel 
                                            x:Name="CellsPresenterElement" 
                                            Orientation="Vertical" ArrangeAccordingToVisibleIndex="True" 
                                            LinesWidth="{TemplateBinding HorizontalLinesThickness}"
                                            ShowLines="{TemplateBinding ShowHorizontalLines}" 
                                            StretchLines="ByPreviousAndNextElements" 
                                            VerticalAlignment="Top" />
                                </ContentControl>
                            </Grid>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>       
        </Style>  
    </ResourceDictionary>

    The easiest way to start is to copy generix.xaml from the AgDataGrid sources into your own themes\generic.xaml. We will keep everything as in the original template except for a view things:

    1: We’ll wrap the VerticalScrollbarElement with a Collapsed Grid panel. This will ensure that we still have the default scrolling logic and the mouse wheel working.

    2:

    ....
    <Setter Property="SelectedCellStyle">
               <Setter.Value>
                   <local:AgStyle>
                       <local:AgSetter Property="Foreground">
                           <local:AgSetter.Value>
                               <SolidColorBrush Color="White"  />
                           </local:AgSetter.Value>
                       </local:AgSetter>
                       <local:AgSetter Property="Background">
                           <local:AgSetter.Value>
                               <SolidColorBrush Color="White" />
                           </local:AgSetter.Value>
                       </local:AgSetter>
                   </local:AgStyle>
               </Setter.Value>
    </Setter>
    ....

    We’ll remove all the highlighting of cells and rows by setting them all to White or whatever you choose your default background to be. This will make the Grid look like it is “not selectable”.

    We can now use it like so:

    ....
    xmlns:Touch="clr-namespace:DevExpress.Exchange.Communicator.Silverlight.Controls"
    xmlns:AgDataGrid="clr-namespace:DevExpress.AgDataGrid;assembly=DevExpress.AgDataGrid.v9.1" >
    ....
    <Grid x:Name="LayoutRoot" Background="White">
            <Touch:AgTouchGrid Margin="1,0,1,1" x:Name="contactList" ColumnsAutoWidth="true" Visibility="Visible" ShowColumnHeaders="Collapsed">
                <AgDataGrid:AgDataGrid.Columns>
                    <AgDataGrid:AgDataGridColumn FieldName="Name" AllowEditing="false">
                        <AgDataGrid:AgDataGridColumn.DisplayTemplate>
                            <ControlTemplate>
                                <Grid Height="40">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"  />
                                        <ColumnDefinition Width="*"  />
                                    </Grid.ColumnDefinitions>
                                    <Grid VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="0">
                                        <Grid>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="32"></ColumnDefinition>
                                                <ColumnDefinition Width="*"></ColumnDefinition>
                                                <ColumnDefinition Width="16"></ColumnDefinition>
                                            </Grid.ColumnDefinitions>
                                            <Image Grid.Column="0"  VerticalAlignment="Center" HorizontalAlignment="Left" x:Name="Icon" Source="{Binding Edit.DataContext.RowValue.Icon}" Width="16" Height="16"  />
                                            <Grid Grid.Column="1" VerticalAlignment="Center">
                                                <Grid.RowDefinitions>
                                                    <RowDefinition  />
                                                    <RowDefinition  />
                                                </Grid.RowDefinitions>
                                                <TextBlock Grid.Row="0"  x:Name="Name" Text="{Binding Edit.DataContext.RowValue.Name}" FontFamily="Verdana" FontSize="12" />
                                                <TextBlock Grid.Row="1"  x:Name="Status" Text="{Binding Edit.DataContext.RowValue.Status}" Foreground="Gray" />
                                            </Grid>
                                        </Grid>
                                    </Grid>
                                </Grid>
                            </ControlTemplate>
                        </AgDataGrid:AgDataGridColumn.DisplayTemplate>
                    </AgDataGrid:AgDataGridColumn>
                </AgDataGrid:AgDataGrid.Columns>
            </Touch:AgTouchGrid>
    </Grid>

    Note: Because of the architecture change, the DataContext of the Column.DisplayTemplate is different then in the Free AgDataGrid. We must use the above notation to access data object properties from within the DisplayTemplate:

    Here, I am binding to an IList<Contact>

    public class Contact {        
            public Guid Id {
                get;
                set;
            }
            public string[] ExtensionList {
                get;
                set;
            }
            public int SortOrder {
                get;
            }
            public string Status {
                get;
            }
            public string Icon {
                get;
            }
            public string Name {
                get;
                set;
            }
     }

    so I must use {Binding Edit.DataContext.RowValue.MyPropertyName} to get to MyPropertyName. 

    Mouse Events and Touch Scrolling

    Idea is simple,

    • Override OnMouseLeftButtonDown, OnMouseLeftButtonUp and OnMouseMove events and calculate the distance traveled.
    • Convert the distance traveled into scroll steps and send this delta to the VerticalScrollbarElement.
    • Optionally, update VerticalScrollbarElement using a storyboard for that slick slide effect.

    private bool _touchScrolling = false; private double _touchStart = 0; protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if(this.VerticalScrollController != null && !_touchScrolling && e.OriginalSource != _pageFooter) { OnTouchStart(e); } else { base.OnMouseLeftButtonDown(e); } } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); OnTouchFinish(e); } private void OnTouchStart(MouseButtonEventArgs e) { _touchScrolling = true; _touchStart = e.GetPosition(this).Y; this.CaptureMouse(); this.DataController.SelectionController.Clear(); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if(this.VerticalScrollController != null && _touchScrolling) { double delta = Math.Ceiling((e.GetPosition(this).Y - _touchStart) / 20); if(Math.Abs(delta) > 2) { this.Cursor = Cursors.Hand; if(Math.Sign(delta) > 0) { AnimatedScroll(+Math.Abs(delta)); } else if(Math.Sign(delta) < 0) { AnimatedScroll(-Math.Abs(delta)); } _touchStart = e.GetPosition(this).Y; } } } private void OnTouchFinish(MouseButtonEventArgs e) { if(this.VerticalScrollController != null && _touchScrolling) { _touchScrolling = false; ReleaseMouseCapture(); double delta = Math.Ceiling((e.GetPosition(this).Y - _touchStart) / (this.ActualHeight / this.VisibleRowCount)); if(Math.Abs(delta) > 0) { if(Math.Sign(delta) > 0) { AnimatedScroll(+Math.Abs(delta)); } else if(Math.Sign(delta) < 0) { AnimatedScroll(-Math.Abs(delta)); } } else { base.OnMouseLeftButtonDown(e); base.OnMouseLeftButtonUp(e);

    // Raise the mouse click event here } } }

    And the AnimatedScroll:

    public void AnimatedScroll(double delta) {
        Storyboard storyBoard = GetTemplateChild("TouchStory") as Storyboard;
        if(storyBoard != null) {
            (storyBoard.Children[0] as DoubleAnimation).From = VerticalScrollController.Value;
            (storyBoard.Children[0] as DoubleAnimation).To = VerticalScrollController.Value + delta;
            (storyBoard.Children[0] as DoubleAnimation).By = 1;
            storyBoard.Begin();
        }
    }

      

    That’s it, press and move your mouse up and down on the Grid’s surface to see the effect. Or if you have a touch screen (wink wink) use your fingers :)

    Download the Complete Source Code:

    Includes:

    • Lightweight connection-oriented request-response library
    • WinForms Client for presence exchange
    • Presence Server
    • TAPI Server (If you have TAPI drivers on your machine and a TAPI enabled device, you will have more fun with this then anyone else :))
    • Simple Policy Server for Silverlight

     

    Next Steps

    The included source code is too big to cover in one post. So please follow alone as I continue to build this cool little app and as result, I will show you lots and lots* of goodies from DXperience 2009 vol1.

     

    Cheers

    Azret

  • Free Silverlight DataGrid is Fast!

    What's New in 8.2.5

    If you have not downloaded the latest version of the AgDataGrid you must! This release (8.2.5) includes some significant changes.

    The grid is much much faster now. W00t!. (I know some of you have been waiting for this.) This was not an easy task. The problem is that any change to the VisualTree is an expensive operation. The less you touch it, the faster things get. With that in mind, the internal StackPanel used by the grid to present the actual data rows, and the internal RowsController have been completely rewritten. Now in 8.2.5, instead of rebuilding the tree as the grid is being scrolled, existing elements are reused and updated with values according to the data source.

    Additionally, a new property was added: DelayScrolling. DelayScrolling helps a lot if you have a lot of records per data page. So as you scroll via the scroll bar, the data will be scrolled via a ticker, thus removing the "scroll delay" effect.

    While we are on the topic of what's new, I should mention that after you upgrade, you will need to change your namespace references as some of them have been renamed.

    • DevExpress.Windows.Data -> DevExpress.AgDataGrid.Data
    • DevExpress.Windows.Data.Helpers -> DevExpress.AgDataGrid.Data.Helpers
    • DevExpress.Windows.Controls.Internal -> DevExpress.AgDataGrid.Internal
    • DevExpress.Windows.Controls -> DevExpress.AgDataGrid
    • DevExpress.Windows.Controls.Tests -> DevExpress.AgDataGrid.Tests
    • DevExpress.Windows.Controls.UIAutomation -> DevExpress.AgDataGrid.UIAutomation

    You can download the latest version by visiting your Client Center area https://www.devexpress.com/ClientCenter/, or if you have not registered for the Free Silverlight Controls yet you can do so at http://devexpress.com/Products/NET/Controls/Silverlight/Register/.

    Now that I got you all excited, I want to share some of the AgDataGrid tips and techniques. Namely, I want to cover the master-details scenarios. What's a RIA without the master-detail data right? :)

    Master-Detail data with 2 Grids

    This one is easy. Imagine 2 grids side by side one displaying customer records and the other one is showing "orders". The orders change as you scroll through the customers.

    Download master records: (Note: I am using AgXPO to fetch the data from the server)

    XPQuery<Customer> source = new XPQuery<Customer>(unitOfWork);
    
    var query =
        from t in source
        select t;
    
    customersGrid.DataSource = query.ToList<Customer>();
    

    then in the FocusedRowChanged event of the customers data grid:

    this.customersGrid.FocusedRowChanged += 
    
         new FocusedRowChangedEventHandler(Customers_FocusedRowChanged); 

    simply:

    XPQuery<Order> source = new XPQuery<Order>(unitOfWork);
    var query =
        from t in source
        where t.CustomerID == customerId
        orderby t.OrderDate descending
        select t;
    
    ordersDataGrid.DataSource = query.ToList<Order>();

    Master-Detail data with 1 Grid

    This one is easy as well. We can use the preview row feature of the AgDataGrid to display the detail records, and fetch the data for it in the PreviewStatus event. So if the grid is bound to the list of

    public class Customer : PersistentBase {
        public Customer(Session session)
            : base(session) {
        }
    
        IList<Order> _orders;
        [NonPersistent]
        public IList<Order> Orders {
            get { return _orders; }
            set { _orders = value; }
        }
    }
    

    Then in the PreviewStatus event, we'll fetch all orders into the Orders property and in the data template for the PreviewRow we'll make sure that we bind to this list.

    <local:AgDataGrid.PreviewTemplate>
        <DataTemplate>
            <local:AgDataGrid DataSource="{Binding Orders}">
        </DataTemplate>
    </local:AgDataGrid.PreviewTemplate>

    I have used both of these techniques in a little Northwind explorer app.

     image

    You can download the full source for it here

     

    Cheers

    Azret

  • What Membership Provider do you use?

    What do you use as your Membership Provider? Chances are you have a SqlMembershipProvider in your Web.config. All your login controls work via the Membership API and all is great. But what if you have an existing user/role db model that is different from the default one generated by aspnet_regsql.exe. And what if your web site is powered by something other than the SQL Server?

    Luckily, the Membership API is extensible enough and allows you to roll your own providers. In my case, I had an existing user/role model that I wanted to use via the Membership API.

    The  Users

    [Persistent("Users")]
    public class UserBase : XPLiteObject, IContactInfo {
        
        public UserBase(Session session)
            : base(session) {
        }
        
        public UserBase(IContactInfo contactInfo, Session session)
            : this(session) {
            if (contactInfo != null) {
                this.Email = contactInfo.Email;
                this.Prefix = contactInfo.Prefix;
                this.DisplayName = contactInfo.DisplayName;
                this.FirstName = contactInfo.FirstName;
                this.LastName = contactInfo.LastName;
                this.Address1 = contactInfo.Address1;
                this.Address2 = contactInfo.Address2;
                this.City = contactInfo.City;
                this.State = contactInfo.State;
                this.PostalCode = contactInfo.PostalCode;
                this.Country = contactInfo.Country;
            }
        }
    
        [NonPersistent]
        public string DisplayNameEx {
            get {
                return this.ToString();
            }
        }
    
        public override string ToString() {
            if (!string.IsNullOrEmpty(this.DisplayName)) {
                return this.DisplayName.Trim();
            }
            string s = this.FirstName;
            if (!string.IsNullOrEmpty(this.LastName)) {
                if (!string.IsNullOrEmpty(s)) {
                    s += " ";
                }
                s += this.LastName.Trim();
            }
            if (!string.IsNullOrEmpty(s)) {
                return s;
            }
            if (!string.IsNullOrEmpty(this.Email)) {
                return this.Email.Trim();
            }
            return this.UserName;
        }
    
        public string ToString(bool displayOnly) {
            if (!string.IsNullOrEmpty(this.DisplayName)) {
                return this.DisplayName.Trim();
            }
            string s = this.FirstName;
            if (!string.IsNullOrEmpty(this.LastName)) {
                if (!string.IsNullOrEmpty(s)) {
                    s += " ";
                }
                s += this.LastName.Trim();
            }
            if (!string.IsNullOrEmpty(s)) {
                return s;
            }
            if (displayOnly) {
                return string.Empty;
            }
            if (!string.IsNullOrEmpty(this.Email)) {
                return this.Email.Trim();
            }
            return this.UserName;
        }
    
        Guid _userId;
        [Key(true)]
        public Guid UserId {
            get {
                return _userId;
            }
            set {
                SetPropertyValue<Guid>("UserId", ref _userId, value);
            }
        }
        string _identityList;
        [Size(256)]
        public string IdentityList {
            get {
                return _identityList;
            }
            set {
                SetPropertyValue<string>("IdentityList", ref _identityList, value);
            }
        }    
        string _userName;
        [Size(256)]
        [Indexed(Unique = true)]
        public string UserName {
            get {
                return _userName;
            }
            set {
                SetPropertyValue<string>("UserName", ref _userName, value);
            }
        }
        string _email;
        [Size(256)]
        [Indexed(Unique = true)]
        public string Email {
            get {
                return _email;
            }
            set {
                SetPropertyValue<string>("Email", ref _email, value);
            }
        }
        string _password;
        [Size(128)]
        public string Password {
            get {
                return _password;
            }
            set {
                SetPropertyValue<string>("Password", ref _password, value);
            }
        }
        string _passwordSalt;
        [Size(128)]
        public string PasswordSalt {
            get {
                return _passwordSalt;
            }
            set {
                SetPropertyValue<string>("PasswordSalt", ref _passwordSalt, value);
            }
        }
        int _passwordFormat;
        public int PasswordFormat {
            get { 
                return _passwordFormat; 
            }
            set {
                SetPropertyValue<int>("PasswordFormat", ref _passwordFormat, value); 
            }
        }
        string _prefix;
        [Size(16)]
        public string Prefix {
            get {
                return _prefix;
            }
            set {
                SetPropertyValue<string>("Prefix", ref _prefix, value);
            }
        }
        string _displayName;
        [Size(128)]
        public string DisplayName {
            get {
                return _displayName;
            }
            set {
                SetPropertyValue<string>("DisplayName", ref _displayName, value);
            }
        }
        string _firstName;
        [Size(64)]
        public string FirstName {
            get {
                return _firstName;
            }
            set {
                SetPropertyValue<string>("FirstName", ref _firstName, value);
            }
        }
        string _lastName;
        [Size(64)]
        public string LastName {
            get {
                return _lastName;
            }
            set {
                SetPropertyValue<string>("LastName", ref _lastName, value);
            }
        }
        string _address1;
        [Size(128)]
        public string Address1 {
            get {
                return _address1;
            }
            set {
                SetPropertyValue<string>("Address1", ref _address1, value);
            }
        }
        string _address2;
        [Size(128)]
        public string Address2 {
            get {
                return _address2;
            }
            set {
                SetPropertyValue<string>("Address2", ref _address2, value);
            }
        }
        string _city;
        [Size(128)]
        public string City {
            get {
                return _city;
            }
            set {
                SetPropertyValue<string>("City", ref _city, value);
            }
        }
        string _state;
        [Size(64)]
        public string State {
            get {
                return _state;
            }
            set {
                SetPropertyValue<string>("State", ref _state, value);
            }
        }
        string _postalCode;
        [Size(16)]
        public string PostalCode {
            get {
                return _postalCode;
            }
            set {
                SetPropertyValue<string>("PostalCode", ref _postalCode, value);
            }
        }
        string _country;
        [Size(64)]
        public string Country {
            get {
                return _country;
            }
            set {
                SetPropertyValue<string>("Country", ref _country, value);
            }
        }
        Guid _themeId;
        public Guid ThemeId {
            get {
                return _themeId;
            }
            set {
                SetPropertyValue<Guid>("ThemeId", ref _themeId, value);
            }
        }
        int _flags;
        public int Flags {
            get {
                return _flags;
            }
            set {
                SetPropertyValue<int>("Flags", ref _flags, value);
            }
        }
    }

    My user object has all the boiler plate fields like User Name, Password, Contact Info etc... plus some special ones that are applicable only to my app, IdentityList (Windows Identities), ThemeId etc..

    The default MembershipUser does not contain these values, so we will need to roll our own as well.

    public class MembershipUserEx : MembershipUser {
        private Guid _themeId;
        public virtual Guid ThemeId {
            get {
                return _themeId;
            }
            set {
                _themeId = value;
            }
        }
    
        IContactInfo _contactInfo;
        public virtual IContactInfo ContactInfo {
            get {
                if (_contactInfo == null) {
                    _contactInfo = new ContactInfo();
                }
                return _contactInfo;
            }
            set {
                _contactInfo = new ContactInfo(value);
            }
        }
    }

     

     

     The MembershipProvider

    There is a couple of methods that we need to implement when inheriting from the MembershipProvider:

    public abstract class MembershipProvider : ProviderBase {
        public abstract string ApplicationName { get; set; }
        public abstract bool EnablePasswordReset { get; }
        public abstract bool EnablePasswordRetrieval { get; }
        public abstract int MaxInvalidPasswordAttempts { get; }
        public abstract int MinRequiredNonAlphanumericCharacters { get; }
        public abstract int MinRequiredPasswordLength { get; }
        public abstract int PasswordAttemptWindow { get; }
        public abstract MembershipPasswordFormat PasswordFormat { get; }
        public abstract string PasswordStrengthRegularExpression { get; }
        public abstract bool RequiresQuestionAndAnswer { get; }
        public abstract bool RequiresUniqueEmail { get; }
        public abstract bool ChangePassword(string username, string oldPassword, string newPassword);
        public abstract bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
        public abstract MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status);
        public abstract bool DeleteUser(string username, bool deleteAllRelatedData);
        public abstract MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords);
        public abstract MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords);
        public abstract MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords);
        public abstract int GetNumberOfUsersOnline();
        public abstract string GetPassword(string username, string answer);
        public abstract MembershipUser GetUser(object providerUserKey, bool userIsOnline);
        public abstract MembershipUser GetUser(string username, bool userIsOnline);
        public abstract string GetUserNameByEmail(string email);
        protected virtual void OnValidatingPassword(ValidatePasswordEventArgs e);
        public abstract string ResetPassword(string username, string answer);
        public abstract bool UnlockUser(string userName);
        public abstract void UpdateUser(MembershipUser user);
        public abstract bool ValidateUser(string username, string password);
    }

    The most important ones are: CreateUser, GetUser and ValidateUser:

    CreateUser

    Just new up a UserBase object and save it.

    using (IDataLayer dataLayer = MembershipConnectionHelper.GetDataLayer(username, 
                    this._connectionString, 
                    AutoCreateOption.DatabaseAndSchema)) {
                    
                    using (Session session = new Session(dataLayer)) {
                        session.BeginTransaction();                
                        try {
                            DateTime time = this.RoundToSeconds(DateTime.UtcNow).ToLocalTime();
    
                            UserBase user = new UserBase(contactInfo, session);
                            user.UserName = username;
                            user.Email = email;
                            user.ThemeId = themeId;
                            user.Password = encodedPassword;
                            user.PasswordSalt = salt;
                            user.PasswordFormat = (int)this.PasswordFormat;
                            user.Save();
                            
                            session.CommitTransaction();
                            
                            status = MembershipCreateStatus.Success;
    
                            return new MembershipUserEx(
                                this.Name,
                                username,
                                user.UserId,
                                email,
                                passwordQuestion,
                                null,
                                isApproved,
                                false,
                                time,
                                time,
                                time,
                                time,
                                DateTime.MinValue,
                                user) {
                                    ThemeId = themeId
                                };
                        }
                        catch {
                            status = MembershipCreateStatus.ProviderError;
                            session.RollbackTransaction();
                            throw;
                        }
                    }
                }            

    A helper class that creates our IDataLayer based on config.

    public static class MembershipConnectionHelper {
            public static IDataLayer GetDataLayer(string hashString, string connectionString, 
                AutoCreateOption autoCreateOption) {
                
                XPDictionary dictionary = new DevExpress.Xpo.Metadata.ReflectionDictionary();
                dictionary.GetDataStoreSchema(typeof(UserBase).Assembly);
    
                IDataStore dataStore = XpoDefault.GetConnectionProvider(connectionString,
                    autoCreateOption);
                
                return new DevExpress.Xpo.ThreadSafeDataLayer(dictionary, dataStore);
    }
        }

    GetUser

    public UserBase GetUser(string username) {
        using (IDataLayer dataLayer = MembershipConnectionHelper.GetDataLayer(username,
            this._connectionString,
            AutoCreateOption.DatabaseAndSchema)) {
        
            using (Session session = new Session(dataLayer)) {
                return session.FindObject<UserBase>(
                    new BinaryOperator("UserName", username));
            }
        }
    }

    ValidateUser

    ValidateUser is also simple. For example my simple validation is to check if the passwords match:

    public override bool ValidateUser(string username, string password) {
            if ((!ValidateParameter(ref username, true, true, true, 256) || 
                !ValidateParameter(ref password, true, true, false, 128))) {
                return false;
            }
            
            UserBase user = GetUser(username);
            if (user == null) {
                return false;
            }
            
            string encodedPassword = this.EncodePassword(
                password, 
                (MembershipPasswordFormat)user.PasswordFormat, 
                user.PasswordSalt);
            
            return encodedPassword.Equals(user.Password);
    }

    After implementing the MembershipProvider we just register it in the Web.config (we'll need to GAC our assembly for it to be loaded properly.)

    <connectionStrings>
        <!-- Generate a custom key pair if the specified password format is "Encrypted" -->
        <!--
        <add name="%CONNECTION_STRING_NAME%" connectionString="%CONNECTION_STRING%" />
        -->
      </connectionStrings>
      <system.web>
        <!-- Generate a custom key pair if the specified password format is "Encrypted" -->
        <!--
        <machineKey
          validationKey='E81B8940E6D6588E20F5A79D2DD35CCE9E3471081D4F40AE330662EB38AE0F64455CB3E277E7AAD92E76A6D8D4332EA736BF31831CC4A11A8A653EEFD98A68DA'
          decryptionKey='0751C7C4A520D221A84AE8541B003AF2ABD047FE12D662D6'
          validation='SHA1' />
        -->
        <membership defaultProvider="%MEMBERSHIP_PROVIDER_NAME%">
          <providers>
            <add
              connectionStringName="%CONNECTION_STRING_NAME%"
              enablePasswordRetrieval="false"
              enablePasswordReset="true"
              requiresQuestionAndAnswer="false"
              applicationName="/"
              requiresUniqueEmail="true"
              passwordFormat="Hashed"
              maxInvalidPasswordAttempts="5"
              minRequiredPasswordLength="1"
              minRequiredNonalphanumericCharacters="0"
              passwordAttemptWindow="10"
              passwordStrengthRegularExpression=""
              name="%MEMBERSHIP_PROVIDER_NAME%"
              type="DevExpress.Private.Web.Xpo.MembershipProvider, DevExpress.Private.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3cc6fdd91321e5a7"  />
          </providers>
        </membership>
      </system.web>

    Download Sample Code

    Cheers,

    Azret

  • Implementing a URL Shrinker

    It seems that the length of URLs gets bigger and bigger every hour.

    Here is a Google Maps URL:

    http://www.google.com/maps?f=q&hl=en&geocode=&q=805+N.Brand+Glendale,+CA+91210&sll=37.0625,-95.677068&sspn=25.704151,53.789062&ie=UTF8&ll=34.154609,-118.245227&spn=0.017419,0.033045&z=16&g=805+N.Brand+Glendale,+CA+91210&iwloc=addr&layer=c&cbll=34.158046,-118.255165&panoid=O08NNpO077m501EOlXSgUw&cbp=12,237.8462640496058,,0,-1.0291286092171708

    Ugly isn't?

    Here is another one I grabbed from a SharePoint site:

    http://fiji/Shared%20Documents/Forms/AllItems.aspx?RootFolder=/Shared%20Documents/Specifications/Silverlight/AgDataGrid&FolderCTID=&View={8405395A-43B7-4666-BE45-CEBCE564CE88}

    Not as bad as the Google Maps' one but unreadable nonetheless. And I am sure a lot of you have seen some that are even longer then that.

    There are services out there that can help with that. http://TinyURL.com is probably the most popular one. Unfortunately, public services like that cannot be used by most companies if the URL in question is an internal one. A URL shrinking service must reside on the corp net. So let's quickly write one.

    Getting Started

    I am going to start with a new Silverlight application and have an ASP.NET Web Project generated for me by the Visual Studio wizard. We'll concentrate on the server side of things first. We will need:

    • A database to store our URL to ID mappings
    • A page that will handle the redirection
    • And some UI that can take in a long URL and shrink it.

    To store the URLs, I am going to use XPO. (MS Access as a back-end during development and something more mature like SQL Server for production)

    The shrinker requires only one table:

       1: namespace Shrinker.Data {
       2:     [OptimisticLocking(false)]
       3:     public class Link : PersistentBase {    
       4:         public Link(Session session)
       5:             : base(session) {
       6:         }
       7:         
       8:         int _id;
       9:         [Key(AutoGenerate = true)]
      10:         public int ID {
      11:             get { return _id; }
      12:             set { SetPropertyValue("ID", ref _id, value); }
      13:         }
      14:  
      15:         string _url;
      16:         [Size(1024)]
      17:         public string Url {
      18:             get { return _url; }
      19:             set { SetPropertyValue("Url", ref _url, value); }
      20:         }
      21:     }
      22: }
    and two routines, Get and Put.
       1: namespace Shrinker {
       2:     public static class Utils {
       3:         static DevExpress.Xpo.DB.IDataStore _store;    
       4:  
       5:         static Utils() {
       6:             string connectionString = DevExpress.Xpo.DB.AccessConnectionProvider.GetConnectionString(
       7:                 HttpContext.Current.ApplicationInstance.Server.MapPath("~\\App_Data\\Shrinker.mdb"));
       8:  
       9:             _store = DevExpress.Xpo.XpoDefault.GetConnectionProvider(connectionString,
      10:                 DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
      11:         }
      12:  
      13:         public static int Put(Uri uri) {
      14:             using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_store))) {
      15:                 var link = unitOfWork.FindObject<Data.Link>(new BinaryOperator("Url", uri.AbsoluteUri));
      16:                 if (link != null && link.ID > 0) {
      17:                     return link.ID;
      18:                 }
      19:  
      20:                 link = new Data.Link(unitOfWork);
      21:                 link.Url = uri.AbsoluteUri;
      22:  
      23:                 unitOfWork.Save(link);
      24:                 unitOfWork.CommitChanges();
      25:  
      26:                 return link.ID;
      27:             };
      28:         }
      29:  
      30:         public static string Get(int id) {
      31:             using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_store))) {
      32:                 var link = unitOfWork.FindObject<Data.Link>(new BinaryOperator("ID", id));                
      33:                 if (link != null && link.ID > 0) {
      34:                     return link.Url;
      35:                 }
      36:             };
      37:             return null;
      38:         }
      39:  
      40:     }
      41: }
    we cab use this now in our Default.aspx. Simply check if there are any params passed, if so try to grab the actual URL, otherwise navigate to a UI page.  (Input validation and error checking omitted for clarity)
       1: protected void Page_Load(object sender, EventArgs e) {
       2:     if (Request.QueryString.Count == 0) {
       3:         Response.Redirect("Shrinker.aspx", true);
       4:         return;
       5:     }
       6:     Response.Redirect(Utils.Get(int.Parse(Request.QueryString[0])));
       7: }

    User Interface

    For the UI, let's create a simple ASP.NET page with ASPxRoundPanel, ASPxTextBox and an ASPxButton on it.

    (I customized it using the BlackGlass Theme)

    and in the Page_Load simply use Utils.Put to store the URL from the text box.

       1: namespace Shrinker.Web {
       2:     public partial class Shrinker : System.Web.UI.Page {
       3:         protected void Page_Load(object sender, EventArgs e) {
       4:             if (IsPostBack || IsCallback) {
       5:                 try {
       6:                     Uri uri = new Uri(urlBox.Text);
       7:                     errorLabel.Visible = false;
       8:                     linkLabel.NavigateUrl = string.Format(ConfigurationSettings.AppSettings["RedirectUrl"], Utils.Put(uri).ToString());
       9:                     linkLabel.Text = linkLabel.NavigateUrl;
      10:                     linkLabel.Visible = true;
      11:                 }
      12:                 catch (Exception error) {
      13:                     linkLabel.Visible = false;
      14:                     errorLabel.Text = error.ToString();
      15:                     errorLabel.Visible = true;
      16:                 }
      17:             }
      18:         }
      19:     }
      20: }
    we'll add the RedirectUrl to Web.config so that we can change on the production server.
       1: <appSettings>
       2:     <add key="RedirectUrl" value="http://localhost:8081?{0}" />
       3: </appSettings>
    
    
    and that's it.
     

    Silverlight Client

    For the Silverlight Client, we will expose the Utils.Put as a WCF web service.

       1: namespace Shrinker.Web {
       2:     [ServiceContract(Namespace = "")]
       3:     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
       4:     public class WebService {
       5:         [OperationContract]
       6:         public string Put(string uriString) {
       7:             return string.Format(
       8:                 ConfigurationSettings.AppSettings["RedirectUrl"],
       9:                 Utils.Put(new Uri(uriString)).ToString());
      10:         }
      11:     }
      12: }
    import it into our Silverlight Project and call it like so:
       1: namespace Shrinker {
       2:     public partial class Page : UserControl {
       3:         private void OnClick(object sender, RoutedEventArgs e) {
       4:             WebService.WebServiceClient client = new Shrinker.WebService.WebServiceClient();
       5:             client.PutCompleted += new EventHandler<Shrinker.WebService.PutCompletedEventArgs>(PutCompleted);
       6:             client.PutAsync(urlBox.Text);
       7:         }
       8:  
       9:         void PutCompleted(object sender, Shrinker.WebService.PutCompletedEventArgs e) {
      10:             if (e.Error == null) {
      11:                 linkBox.Text = e.Result;
      12:             }
      13:             else {
      14:                 linkBox.Text = e.Error.ToString();
      15:             }
      16:         }
      17:     }
      18: }
    The layout for the Page.xaml is done using the AgLayoutControl
       1: <layout:LayoutControl Background="#C9D7DC" VerticalAlignment="Top" Margin="0,32,0,0"  Orientation="Vertical">
       2:             <layout:LayoutGroup VerticalAlignment="Top" HorizontalAlignment="Stretch" Orientation="Horizontal">
       3:                 <TextBlock Text="URL:" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock>
       4:                 <TextBox x:Name="urlBox" HorizontalAlignment="Stretch" Text="http://"></TextBox>
       5:                 <Button Content="Shrink" HorizontalAlignment="Right" Style="{StaticResource ButtonStyle}" Width="24" Height="24" 
       6:                         Click="OnClick"></Button>
       7:             </layout:LayoutGroup>
       9:             <TextBox x:Name="linkBox" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Text="http://"></TextBox>
      10: </layout:LayoutControl>
    
    
     

    Bonus

    After you deploy the Shrinker to your corp net. (Assuming you added all the error handling and switched to a SQL Server) you can have it always available for you in the System Tray by running the Shrinker app outside of the browser.

    Download the http://community.devexpress.com/blogs/theonewith/fiji.zip and run it with the URL of your .xap file as a command line parameter.

    For example: "fiji.exe http://localhost:8081/Silverlight/Shrinker.xap"

    Download Source

    Cheers,

    Azret

     
  • Silverlight on a Desktop

    Silverlight or WPF/E (WPF/Everywhere) was built to be a browser plugin. Small, secure and rich*. Why? I mean why stop at the browser? Why can't we deploy it in our own sandbox? Just imagine a brand new class of applications (or applets as I like to call them).

    At this year's PDC, rumors were flying around about *good* Silverlight Hosting API possibilies in the next version. But even then, how would they work? Would they be cross platform?

    One solution to enable this new class of applications is to host everything inside Trident (IE WebBrowser). This works if you want to run your app.on Windows only. To me, this particular class of apps would need to be cross platform to succeed. Even if you disagree and say you are fine with Windows only, you'll still be limited to the Trident's sandbox. Accessing the local system would be possible via window.extern but you'll need to solve the "same origin" (same domain) security restrictions if your xap is gonna be run from say (res:// or file://). And then on top of that, what are you gonna use to host the WebBrowser (Trident), WinForms? If so go straight up WinForms or WPF.

    What else can we do? NPAPI to the rescue. Why not pretend to be a browser and load the Silverlight plugin just like a browser would. Gecko and WebKit based browsers do just that. They use the public Netscape Plugin API to load the plugins that the page needs, based on the MIME type specified in the embed. In our case it's the application/x-silverlight type. By going this route, we can control the sandbox, we can feed the functions to our applets to enable access to local system resources. We can control the same domain security restrictions. And finally, we can be cross platform and we don't need the hosting API even if it existed.

    How cool would it be if I could include a .target file in my Silverlight project, and that .target would include all the steps needed to pack my xap into a single executable?

    Welcome to project Fiji. An experimental work to do just that.

    You can download the preview bits (fiji.exe) here http://community.devexpress.com/blogs/theonewith/fiji.zip.

    To run it, simply pass in the name of your xap file in the command line: "fiji.exe C:\MySilverlightApplication.xap". (full path)

    Would love to hear what you think.

    Cheers

    Azret

  • Silverlight Line of Business Applications: Part 1 - Getting Started

    By now, those of you who know me personally or through the forums and blogs know that I love Silverlight. It had me at hello. A rich client side user experience, cross platform*, tiny runtime. What's not to love? In this set of posts, I want to show you some of the things that you would need, to develop a client side applet for your next big product.

    For this, we are going to develop and deploy a bug tracking app. "Bugger" and we'll use XPO for Silverlight to connect to our data. If you have not read the Silverlight and Data Access using XPO I suggest you do so as it covers the basics. The article is written for DXperience v8.2, and in v8.3, a few things have been changed. For now, the only thing we need to worry about is the name change of the data acess assemblies.

    ·    DevExpress.Data.v8.2.SL was renamed to DevExpress.AgData.v8.3

    ·    DevExpress.Xpo.v8.2.SL was renamed to  DevExpress.AgXpo.v8.3 

    Getting Started

    We'll start by creating a new ASP.NET project and add a Silverlight-enabled WCF Service to it. The web service will expose the XPO DataStore that our client will use.

          [ServiceContract(Namespace = ""), AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

          [XmlSerializerFormat]

          public class Gateway {

                static DevExpress.Xpo.DB.IDataStore s_dataStore;

     

                static Gateway() {

                      try {                  

                            string connectionString = DevExpress.Xpo.DB.AccessConnectionProviderMultiUserThreadSafe.GetConnectionString(

                                  "C:\\Bugger.mdb", "Admin", string.Empty);

                            s_dataStore = DevExpress.Xpo.XpoDefault.GetConnectionProvider(

                                  connectionString,

                                  DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);

                      }

                      catch (Exception e) {

                            System.Diagnostics.Debug.WriteLine(e.Message);

                      }

                }

     

                [OperationContract]

                public DevExpress.Xpo.DB.AutoCreateOption GetAutoCreateOption() {

                      return s_dataStore.AutoCreateOption;

                }

     

                [OperationContract]

                public DevExpress.Xpo.DB.SelectedData SelectData(DevExpress.Xpo.DB.SelectStatement[] selects) {

                      if (selects != null && selects.Length > 0 && selects[0].TableName == "XPObjectType") {

                            return new DevExpress.Xpo.DB.SelectedData(new DevExpress.Xpo.DB.SelectStatementResult());

                      }

                      return s_dataStore.SelectData(selects);

                }

     

                [OperationContract]

                public DevExpress.Xpo.DB.UpdateSchemaResult UpdateSchema(bool dontCreateIfFirstTableNotExist,

                      DevExpress.Xpo.DB.DBTable[] tables) {

                      try {

                            return s_dataStore.UpdateSchema(dontCreateIfFirstTableNotExist, tables);

                      }

                      catch (Exception e) {

                            System.Diagnostics.Debug.WriteLine(e.Message);

                            return DevExpress.Xpo.DB.UpdateSchemaResult.FirstTableNotExists;

                      }

                }

     

                [OperationContract]

                public DevExpress.Xpo.DB.ModificationResult ModifyData(DevExpress.Xpo.DB.ModificationStatement[] statements) {

                      try {

                            return s_dataStore.ModifyData(statements);

                      }

                      catch (Exception e) {

                            System.Diagnostics.Debug.WriteLine(e.Message);

                            return null;

                      }

                }

          }

    We will need to add references to DevExpress.Data.v8.3 and DevExpress.Xpo.v8.3 assemblies.

    Client Side

    On the client side, we will need to add a service reference to this Gateway web service. Instead of using the wizard however, we'll do this manually by adding a generic client proxy implementation (Gateway.cs) to our Silverlight project, downloadable here http://tv.devexpress.com/content/XPO/Gateway/Gateway.zip. We'll update the ServiceReferences.ClientConfig so that our IGateway contract is registered properly.

     <configuration>

      <system.serviceModel>

        <bindings>

          <basicHttpBinding>

            <binding name="BasicHttpBinding_Gateway" maxBufferSize="65536"

              maxReceivedMessageSize="65536">

              <security mode="None" />

            </binding>

          </basicHttpBinding>

        </bindings>

        <client>

          <endpoint address="http://localhost:50344/Gateway.svc" binding="basicHttpBinding"

            bindingConfiguration="BasicHttpBinding_Gateway" contract="DevExpress.Xpo.Xtras.IGateway"

            name="BasicHttpBinding_Gateway" />

        </client>

      </system.serviceModel>

    </configuration>

     

    Now we are ready to design our data model and start running some queries. Our data model is rather simple here. We have only one table Issues that we want to query, add to and display on the screen.

          public enum Status {

                Active,

                Closed,

          }

     

          public enum Importance {

                Normal,

                High,

                Low,

          }

     

          [OptimisticLocking(false)]

          public class Issue : PersistentBase {

                public Issue(Session session)

                      : base(session) {

                }

                Guid _id;

                [Key(AutoGenerate = true)]

                public Guid ID {

                      get { return _id; }

                      set { SetPropertyValue("ID", ref _id, value); }

                }

                string _subject;

                public string Subject {

                      get { return _subject; }

                      set { SetPropertyValue("Subject", ref _subject, value); }

                }

                string _owner;

                public string Owner {

                      get { return _owner; }

                      set { SetPropertyValue("Owner", ref _owner, value); }

                }

                Status _status;

                public Status Status {

                      get { return _status; }

                      set { SetPropertyValue("Status", ref _status, value); }

                }

                Importance _importance;

                public Importance Importance {

                      get { return _importance; }

                      set { SetPropertyValue("Importance", ref _importance, value); }

                }

                [NonPersistent]

                public Visibility HighImportance {

                      get {

                            if (Importance == Importance.High) {

                                  return Visibility.Visible;

                            }

                            else {

                                  return Visibility.Collapsed;

                            }

                      }

                }

                [NonPersistent]

                public Visibility LowImportance {

                      get {

                            if (Importance == Importance.Low) {

                                  return Visibility.Visible;

                            }

                            else {

                                  return Visibility.Collapsed;

                            }

                      }

                }

          }

    Notice the additional NonPersistent helper properties. They will come in handy when displaying data inside the AgDataGrid.

    Since we dont have any data entry forms yet and we need some mock data. Let's add some fake issues. Remember any work done againt XPO must be done asynchronously.

                             DevExpress.Xpo.Xtras.Gateway _dataStore;

                 ..

                 this._dataStore = new DevExpress.Xpo.Xtras.Gateway(this.Dispatcher);         

                 .. 

                ThreadPool.QueueUserWorkItem((state) => {

                            try {

                                  using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(this._dataStore))) {

     

                                        var issue = new Issue(unitOfWork);

                                        issue.Owner = "Joe Smith";

                                        issue.Status = Status.Active;

                                        issue.Subject = "New Issue";

     

                                        unitOfWork.Save(issue);

                                        unitOfWork.CommitChanges();

                                  };

                            }

                            catch (Exception e) {

                                  System.Diagnostics.Debug.WriteLine(e.Message);

                            }

                      });

    Displaying the Data

    Download the free AgDataGrid and AgMenu suites here http://devexpress.com/Products/NET/Controls/Silverlight/Menu/ we'll use them a lot. Add a reference to the AgDataGrid assembly to your project and add a name space alias to it inside your page.xaml. In this case AgDataGridEx is a derived class from the standard AgDataGrid and the namespace alias is our project itself. 

          public class AgDataGridEx : AgDataGrid {

                public AgDataGridEx() {

                      this.DefaultStyleKey = typeof(AgDataGridEx);

                }

          } 

     

          <bugger:AgDataGridEx Grid.Row="1" x:Name="issuesDataGrid" ColumnsAutoWidth="True" ShowGroupPanel="Visible" SelectionMode="None">

                <bugger:AgDataGridEx.Resources>

                    <DataTemplate x:Key="ImportanceCellTemplateEx">

                        <Grid HorizontalAlignment="Center" Margin="0,0,2,0">

                            <Image Source="ImportanceRed.png" Stretch="None" Visibility="{Binding CellValue}"></Image>

                        </Grid>                   

                    </DataTemplate>

                </bugger:AgDataGridEx.Resources>

                <bugger:AgDataGridEx.Columns>

                    <bugger:AgDataGridColumnEx x:Name="issuesColumnImportance" FieldName="HighImportance" FixedWidth="True" Width="22"

                            MinColumnWidth="22" MaxColumnWidth="22" AllowResizing="False" AllowGrouping="False"