Smart Office SDK – First Project Part 5 – styleTextBoxBrowse

Update, please see Karins comments below for a far better solution 🙂

The browse TextBoxes – we’re all familiar when them. A nice clean way of providing lookups attached to a TextBox.

I have a number of these in my existing Vessel Modification and as previously mentioned I want to create a nice seamless user experience – this unfortunately means that we need to replicate this functionality. A quick browse through the SDK DevelopersGuide.pdf I see that they have a section of “Application Resources” and under it there is the styleTextBoxBrowse, described as “Named TextBox style that includes a browse button. The browse button is a small arrow to the right point to the right”. Sounds like us!


So, a minute or two (or a bit longer) later I’ve applied the style to an existing TextBox and I end up with something like this:

The XAML for a TextBox looking like this:

<TextBox Height=”18″ HorizontalAlignment=”Left” Margin=”131,355,0,0″ Name=”tbPCVessel” Style=”{StaticResource styleTextBoxBrowse}” VerticalAlignment=”Top” Width=”92″ />

A quick skim through the pdf and I can’t find a way to subscribe to the click event of the button. I also have a look through the SDK helpfile – again, can’t find anything useful.

At this point I dig out “Inspect” – it is a tool that is distributed with the Microsoft SDK (I have the 7.1 version). This tool allows you to inspect the visual compontents of running applications.

We can click on a control on an application and it will give us some detailed information – so if I click on the TextBox with the browse style I get this:

We can see the control is indeed a normal TextBox and we can see the Name that I have given it in the AutomationId (tbVessel).

Scrolling further down we see these gems

So we now know that there is no black magic here, there is indeed a button. I’ve posted a question on the http://lawsonsmartoffice.com/ for the ‘proper’ way but figured I’d investigate anyway 🙂

It doesn’t really help us that much at this point, but we can see that the button is a child – so I’d assume that I’d be able to use the VisualTreeHelper technique that I had been using a long time ago to browse the tree of objects to get information.

So I quickly hacked up some code based on the JScript I written and ran it against the user control I had created which has the TextBoxes in it.

        private object displayChildren(StringBuilder asbBuilder, object aobjParent, int aiDepth)
        {
            object result = null;

            try
            {
                if (null != aobjParent)
                {
                    Type tpParentType = aobjParent.GetType();
                    if (tpParentType.IsSubclassOf(typeof(DependencyObject)) == true)
                    {
                        for (int i = 0; i < VisualTreeHelper.GetChildrenCount((DependencyObject)aobjParent); i++)
                        {
                            object objCurrent = VisualTreeHelper.GetChild((DependencyObject)aobjParent, i);
                            if (null != objCurrent)
                            {
                                var objType = objCurrent.GetType();
                                var objPropertyName = objType.GetProperty("Name");

                                string strPadding = "";
                                for (int j = 0; j < (aiDepth * 4); j++)                                 {                                     strPadding += " ";                                 }                                 if (null != objPropertyName)                                 {                                     asbBuilder.AppendLine(strPadding + "Object Name: '" + objPropertyName.GetValue(objCurrent, null) + "'");                                 }                                 else                                 {                                     asbBuilder.AppendLine(strPadding + "Object Name is unknown");                                 }                                 asbBuilder.AppendLine(strPadding + "Object Type: " + objType.ToString());                                 if (VisualTreeHelper.GetChildrenCount((DependencyObject)objCurrent) >= 1)
                                {
                                    displayChildren(asbBuilder, objCurrent, aiDepth + 1);
                                }
                            }

                        }
                    }
                }
            }
            catch(Exception)
            {

            }

            return (result);
        }

Called when we hit the Save button

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {

            StringBuilder sbNew = new StringBuilder(5000);
            displayChildren(sbNew, tbPCVoyage, 0);
            Console.WriteLine(sbNew.ToString());
        }

Note: It is important that do this after the Constructor for our user control – which is why I use the Save button for testing.

So I will only examine from the TextBox itself down and then output the text to the console and it looks like this:

Object Name: ‘GridText’

Object Type: System.Windows.Controls.Grid

Object Name: ‘BorderFocusInner’

Object Type: System.Windows.Controls.Border

Object Name: ‘BorderFocusOuter’

Object Type: System.Windows.Controls.Border

Object Name: ‘Border’

Object Type: System.Windows.Controls.Border

Object Name: ”

Object Type: System.Windows.Controls.Grid

Object Name: ‘Top_gradient’

Object Type: System.Windows.Shapes.Rectangle

Object Name: ‘PART_ContentHost’

Object Type: System.Windows.Controls.ScrollViewer

Object Name: ”

Object Type: System.Windows.Controls.Border

Object Name: ”

Object Type: System.Windows.Controls.Grid

Object Name: ”

Object Type: System.Windows.Controls.ScrollContentPresenter

Object Name: ”

Object Type: System.Windows.Controls.TextBoxView

Object Name is unknown

Object Type: System.Windows.Controls.TextBoxLineDrawingVisual

Object Name: ”

Object Type: System.Windows.Documents.AdornerLayer

Object Name: ‘PART_VerticalScrollBar’

Object Type: System.Windows.Controls.Primitives.ScrollBar

Object Name: ‘PART_HorizontalScrollBar’

Object Type: System.Windows.Controls.Primitives.ScrollBar


Object Name: ‘BrowseButton’

Object Type: System.Windows.Controls.Button

Object Name: ‘borderBrowse’

Object Type: System.Windows.Controls.Border

Object Name: ‘imageButtonBrowse’

Object Type: System.Windows.Controls.Image

So we see that we can find the button. It shouldn’t be too hard to attach an event to that button – we just need a function to find the control itself.

        private object findChild(Object aobjParent, string astrName)
        {
            object result = null;

            try
            {
                if (null != aobjParent)
                {
                    Type tpParentType = aobjParent.GetType();
                    if (tpParentType.IsSubclassOf(typeof(DependencyObject)) == true)
                    {
                        for (int i = 0; i < VisualTreeHelper.GetChildrenCount((DependencyObject)aobjParent); i++)                         {                             object objCurrent = VisualTreeHelper.GetChild((DependencyObject)aobjParent, i);                             if (null != objCurrent)                             {                                 var objType = objCurrent.GetType();                                 var objPropertyName = objType.GetProperty("Name");                                 if (null != objPropertyName)                                 {                                     string strName = (string)objPropertyName.GetValue(objCurrent, null);                                     if (strName == astrName)                                     {                                         result = objCurrent;                                         break;                                     }                                 }                                 if (VisualTreeHelper.GetChildrenCount((DependencyObject)objCurrent) >= 1)
                                {
                                    result = findChild(objCurrent, astrName);
                                }
                            }

                        }
                    }
                }
            }
            catch (Exception)
            {

            }

            return (result);
        }

And we’d call it like this:

            object objButton = findChild(tbPCVoyage, "BrowseButton");

            if (null != objButton)
            {
                MessageBox.Show("Found button");
            }

“tbPCVoyage” is the textbox that we are looking for the button of.
“BrowseButton” is the name of the button control which we discovered from our output.

I run it and whala! I have found the control!

The issue then becomes knowing when to bind to the control. In the LaunchTask() I add an event to my controls Loaded event. Then I call a method (<vesselcontrol>.SubscribeToBrowseEvents()) to subscribe to the click events on the appropriate browse button.

The user control source:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Vessel
{
    ///
/// Interaction logic for UserControl1.xaml
    ///
    public partial class VesselControl : UserControl
    {
        public VesselControl()
        {
            InitializeComponent();
        }

        ///
/// Find the child control with a specific name
        ///
        ///the UIElement control that we will start the search from
        ///the name of the control we are looking for
        /// the control whose name matches astrName or null
        private object findChild(Object aobjParent, string astrName)
        {
            object result = null;

            try
            {
                if (null != aobjParent)
                {
                    Type tpParentType = aobjParent.GetType();
                    if (tpParentType.IsSubclassOf(typeof(DependencyObject)) == true)
                    {
                        for (int i = 0; i < VisualTreeHelper.GetChildrenCount((DependencyObject)aobjParent); i++)                         {                             object objCurrent = VisualTreeHelper.GetChild((DependencyObject)aobjParent, i);                             if (null != objCurrent)                             {                                 var objType = objCurrent.GetType();                                 var objPropertyName = objType.GetProperty("Name");                                 if (null != objPropertyName)                                 {                                     string strName = (string)objPropertyName.GetValue(objCurrent, null);                                     if (strName == astrName)                                     {                                         result = objCurrent;                                         break;                                     }                                 }                                 if (VisualTreeHelper.GetChildrenCount((DependencyObject)objCurrent) >= 1)
                                {
                                    result = findChild(objCurrent, astrName);
                                }
                            }

                        }
                    }
                }
            }
            catch (Exception)
            {

            }

            return (result);
        }

        private object displayChildren(StringBuilder asbBuilder, object aobjParent, int aiDepth)
        {
            object result = null;

            try
            {
                if (null != aobjParent)
                {
                    Type tpParentType = aobjParent.GetType();
                    if (tpParentType.IsSubclassOf(typeof(DependencyObject)) == true)
                    {
                        for (int i = 0; i < VisualTreeHelper.GetChildrenCount((DependencyObject)aobjParent); i++)
                        {
                            object objCurrent = VisualTreeHelper.GetChild((DependencyObject)aobjParent, i);
                            if (null != objCurrent)
                            {
                                var objType = objCurrent.GetType();
                                var objPropertyName = objType.GetProperty("Name");

                                string strPadding = "";
                                for (int j = 0; j < (aiDepth * 4); j++)                                 {                                     strPadding += " ";                                 }                                 if (null != objPropertyName)                                 {                                     asbBuilder.AppendLine(strPadding + "Object Name: '" + objPropertyName.GetValue(objCurrent, null) + "'");                                 }                                 else                                 {                                     asbBuilder.AppendLine(strPadding + "Object Name is unknown");                                 }                                 asbBuilder.AppendLine(strPadding + "Object Type: " + objType.ToString());                                 if (VisualTreeHelper.GetChildrenCount((DependencyObject)objCurrent) >= 1)
                                {
                                    displayChildren(asbBuilder, objCurrent, aiDepth + 1);
                                }
                            }

                        }
                    }
                }
            }
            catch(Exception)
            {

            }

            return (result);
        }

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine(VisualTreeHelper.GetChildrenCount(tbPCVoyage));

            //object objButton = findChild(tbPCVoyage, "BrowseButton");

            //if (null != objButton)
            //{
            //    MessageBox.Show("Found button");
            //}

            StringBuilder sbNew = new StringBuilder(5000);
            displayChildren(sbNew, tbPCVoyage, 0);
            Console.WriteLine(sbNew.ToString());
        }

        public void SubscribeToBrowseEvents()
        {
            object objButton = findChild(tbPCVoyage, "BrowseButton");

            if (null != objButton)
            {
                MessageBox.Show("Found button");
                ((Button)objButton).Click += new RoutedEventHandler(VesselControl_Click);
            }

        }

        void VesselControl_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Browse Button was pressed");
        }

    }
}

And the feature source:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Mango.Services;
using Mango.UI;
using Mango.UI.Core;
using Mango.UI.Services;
using Mango.UI.Services.Mashup;

using Vessel;

namespace VesselMod
{
    public sealed class VesselModApplication : ClientApplicationBase, IClientApplicationFactory
    {
        private static readonly log4net.ILog logger =
            Mango.Core.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        // the custom control that we will be displaying
        VesselControl gvcControl = new VesselControl();

        #region IClientApplicationFactory Members

        public IClientApplication Create()
        {
            // The code needed to register your mashup control with the Mashup Designer
            /*
            MashupService.RegisterControl(new MashupControlInformation()
            {
                Application = "",
                ApplicationDescription = "<a>",
                Prefix = "</a>",
                NamespaceURI = "clr-namespace:VesselMod;assembly=",
                Name = "",
                Description = "A description for the control",

                // It is also possible to add application templates and examples using the "Examples" property
            });
            */

            State = ApplicationState.Running;
            return this;
        }

        #endregion

        #region IClientApplication	Members

        public override IRunner LaunchTask(ITask task, ILaunchProvider provider)
        {
            //	Use the provider to create	a runner	for our task.
            IRunner runner = provider.CreateRunner(task);

            //	Create a	default host for the	user interface.
            provider.CreateHost(runner);

            //	Get a	reference to the created host.
            IInstanceHost host = runner.Host;

            //	Set the default size	for the host window
            host.Width = 640;
            host.Height = 480;

            //	Create and set	the content	for the host
            //var content = new TextBlock()
            //{
            //    Text = "VesselMod",
            //    HorizontalAlignment = HorizontalAlignment.Center,
            //    VerticalAlignment = VerticalAlignment.Center,
            //    FontSize = 24
            //};

            host.HostContent = gvcControl; //content;

            //	Set the visible name	for task	and the title for	the host	window.
            string visibleName = "VesselMod";
            task.VisibleName = visibleName;
            task.UseVisibleShortName = false;
            host.HostTitle = visibleName;

            //	Change the status	of	the runner from the default value Pending	to	Running.
            runner.Status = RunnerStatus.Running;

            //	Make the	host window	visible on the	Canvas.
            host.Show();

            // subscribe the loaded event of our UserControl
            // this is fired when the control is rendered and ready for interaction
            gvcControl.Loaded += new RoutedEventHandler(vcControl_Loaded);

            return runner;
        }

        void vcControl_Loaded(object sender, RoutedEventArgs e)
        {
            // subscribe to our buttons events
            gvcControl.SubscribeToBrowseEvents();
        }

        public override string VisibleName
        {
            get { return "VesselMod"; }
        }

        #endregion
    }
}

Now we shall wait and see if there is a better way to achieve the same results – I’ll update this posting if there is 🙂

Cheers,
Scott

This entry was posted in Development, M3 / MoveX, SDK and tagged . Bookmark the permalink.

4 Responses to Smart Office SDK – First Project Part 5 – styleTextBoxBrowse

  1. karinpb says:

    Hi Scott. I’m a bit confused. Would you like to handle the click event on a button? A click is a routed command. You don’t need to find the button. If you use AddHandler on the textbox or any other parent element you will get the click event.

    With the AddHandler you can also get handled events. http://msdn.microsoft.com/en-us/library/system.windows.uielement.addhandler(v=vs.100).aspx

    For a button click it will look like this:
    yourUIElement.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(OnClickBrowse));

    private void OnClickBrowse(object sender, RoutedEventArgs e)
    { // code here
    }

    Ok I can have some typos becuase that is not real code, but that should be it. Try it out! You can have one handler for all buttons if you like :-). Just make sure you remove the handlers as well if you have them in a long lived object.

    • potatoit says:

      Wow! Now that is pretty awesome.

      In my instance I used:
      tbPCVoyage.AddHandler(Button.ClickEvent, new RoutedEventHandler(VesselControl_Click));
      and it magically just worked.

      I had assumed that I needed to subscribe directly to the button object. That is pretty kewl!

      Thank you, makes life much easier 🙂

  2. Sam N says:

    Hi Scott,
    I have been trying to use a MessageBox like this

    MessageBox.Show (“You must enter a name.”, “Name Entry error”, MessageBoxButtons.YesNo, MessageBoxIcon.Information);

    I get this error when I try to complie it. Have you seen an example of a dialog box or yes or no message box in JSCRIPT?

    Thanks Sam

    • potatoit says:

      Hi Sam,

      the error message didn’t appear in the post. However, if you want to be consistent with the Smart Office look and feel then I would suggest that you use the ConfirmDialog dialogs.

      So something like:
      ConfirmDialog.ShowQuestionDialogNeverHidden(, )

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s