Blogs

The One With

August 2009 - Posts

  • 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

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.