A day with .Net

My day to day experince in .net

UI Automation Provider for a Bar Chart

Posted by vivekcek on January 13, 2015

This post is based on an article in MSDN (https://code.msdn.microsoft.com/C-Windows-7-UIA-Provider-af7a6e31).

I made the source code available at MSDN, straight forward for better understnading.

Extending the chart control.

 public class MyChart : Chart
    {
        public MyChart(string labelX, string labelY, int yUpperValue)
            : base(labelX, labelY, yUpperValue)
        {

        }

        private ChartProvider provider;

        protected override void WndProc(ref Message m)
        {
            const int WM_GETOBJECT = 0x003D;

            if ((m.Msg == WM_GETOBJECT) && (m.LParam.ToInt32() ==
                AutomationInteropProvider.RootObjectId))
            {
                m.Result = AutomationInteropProvider.ReturnRawElementProvider(
                        this.Handle, m.WParam, m.LParam,
                        this.Provider);
                return;
            }
            base.WndProc(ref m);
        }

        private ChartProvider Provider
        {
            get
            {
                if (this.provider == null)
                {
                    this.provider = new ChartProvider(this);
                }

                return this.provider;
            }
        }
    }

The chart provider, our root.

public class ChartProvider : IRawElementProviderFragmentRoot
    {

        MyChart _chart;

        public ChartProvider(MyChart chart)
        {
            _chart = chart;
        }

        #region IRawElementProviderSimple Members

        /// <summary>
        /// Retrieves an object that provides support for a control  pattern on a UI Automation element.
        /// </summary>
        /// <param name="patternId">Identifier of the pattern.</param>
        /// <returns>
        /// Object that implements the pattern interface, or null if the pattern is not supported.
        /// </returns>
        object IRawElementProviderSimple.GetPatternProvider(int patternId)
        {
            return null;
        }

        /// <summary>
        /// Retrieves the value of a property supported by the UI Automation provider.
        /// </summary>
        /// <param name="propertyId">The property identifier.</param>
        /// <returns>
        /// The property value, or a null if the property is not supported by this provider, or <see cref="F:System.Windows.Automation.AutomationElementIdentifiers.NotSupported"/> if it is not supported at all.
        /// </returns>
        object IRawElementProviderSimple.GetPropertyValue(int propertyId)
        {

            if (propertyId == AutomationElementIdentifiers.ControlTypeProperty.Id)
            {
                return ControlType.Custom.Id;
            }

            if (propertyId == AutomationElementIdentifiers.LocalizedControlTypeProperty.Id)
            {
                return "Bar Chart";
            }
            else if (propertyId == AutomationElementIdentifiers.NameProperty.Id ||
                     propertyId == AutomationElementIdentifiers.AutomationIdProperty.Id)
            {
                return _chart.Name;
            }
            else if (propertyId == AutomationElementIdentifiers.IsKeyboardFocusableProperty.Id)
            {
                bool canFocus = false;
                _chart.Invoke(new MethodInvoker(() => { canFocus = _chart.CanFocus; }));
                return canFocus;
            }
            else if (propertyId == AutomationElementIdentifiers.ClassNameProperty.Id)
            {
                return _chart.GetType().ToString();
            }
            else if (propertyId == AutomationElementIdentifiers.IsEnabledProperty.Id)
            {
                return _chart.Enabled;
            }

            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return true;
            }

            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return true;
            }

            return null;
        }

        /// <summary>
        /// Gets a base provider for this element.
        /// </summary>
        /// <value></value>
        /// <returns>The base provider, or null.</returns>
        IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider
        {
            get
            {
                IntPtr hwnd = IntPtr.Zero;
                _chart.Invoke(new MethodInvoker(() => { hwnd = _chart.Handle; }));
                return AutomationInteropProvider.HostProviderFromHandle(hwnd);
            }
        }

        /// <summary>
        /// Gets a value that specifies characteristics of the UI Automation provider; for example, whether it is a client-side or server-side provider.
        /// </summary>
        /// <value></value>
        /// <returns>Either <see cref="F:System.Windows.Automation.Provider.ProviderOptions.ClientSideProvider"/> or <see cref="F:System.Windows.Automation.Provider.ProviderOptions.ServerSideProvider"/>.</returns>
        ProviderOptions IRawElementProviderSimple.ProviderOptions
        {
            get
            {
                return ProviderOptions.ServerSideProvider | ProviderOptions.UseComThreading;
            }
        }

        #endregion

        #region IRawElementProviderFragment Members

        /// <summary>
        /// Gets the bounding rectangle of this element.
        /// </summary>
        /// <value></value>
        /// <returns>The bounding rectangle, in screen coordinates.</returns>
        System.Windows.Rect IRawElementProviderFragment.BoundingRectangle
        {
            get
            {
                return System.Windows.Rect.Empty;
            }
        }

        /// <summary>
        /// Retrieves the root node of the fragment.
        /// </summary>
        /// <value></value>
        /// <returns>The root node. </returns>
        IRawElementProviderFragmentRoot IRawElementProviderFragment.FragmentRoot
        {
            get
            {
                return this;
            }
        }

        /// <summary>
        /// Retrieves an array of fragment roots that are embedded in the UI Automation element tree rooted at the current element.
        /// </summary>
        /// <returns>An array of root fragments, or null.</returns>
        IRawElementProviderSimple[] IRawElementProviderFragment.GetEmbeddedFragmentRoots()
        {
            return null;
        }

        /// <summary>
        /// Retrieves the runtime identifier of an element.
        /// </summary>
        /// <returns>
        /// The unique run-time identifier of the element.
        /// </returns>
        int[] IRawElementProviderFragment.GetRuntimeId()
        {
            return null;
        }

        /// <summary>
        /// Retrieves the UI Automation element in a specified direction within the tree.
        /// </summary>
        /// <param name="direction">The direction in which to navigate.</param>
        /// <returns>
        /// The element in the specified direction, or null if there is no element in that direction
        /// </returns>
        IRawElementProviderFragment IRawElementProviderFragment.Navigate(NavigateDirection direction)
        {
            switch (direction)
            {
                case NavigateDirection.FirstChild:
                    {
                        int cBars = _chart.GetBarCount();
                        if (cBars > 0)
                        {
                            return new ChartBarProvider(_chart, this, 0);
                        }

                        break;
                    }
                case NavigateDirection.LastChild:
                    {
                        // Return the last child, which is the last bar in the chart.
                        int cBars = _chart.GetBarCount();
                        if (cBars > 0)
                        {
                            return new ChartBarProvider(_chart, this, cBars - 1);
                        }

                        break;
                    }

            }

            return null;
        }

        /// <summary>
        /// Responds to a client request to set the focus to this control.
        /// </summary>
        /// <remarks>Setting focus to the control is handled by the parent window.</remarks>
        void IRawElementProviderFragment.SetFocus()
        {
            _chart.Invoke(new MethodInvoker(() =>
            {
                if (_chart.CanFocus)
                {
                    _chart.Focus();
                }
            }));
        }


        #endregion

        #region IRawElementProviderFragmentRoot Members

        /// <summary>
        /// Retrieves the element in this fragment that is at the specified point.
        /// </summary>
        /// <param name="x">The X coordinate,.</param>
        /// <param name="y">The Y coordinate.</param>
        /// <returns>
        /// The provider for the child element at the specified point, if one exists, or the root provider if the point is on this element but not on any child element. Otherwise returns null.
        /// </returns>
        IRawElementProviderFragment IRawElementProviderFragmentRoot.ElementProviderFromPoint(double x, double y)
        {
            // Convert screen point to client point.
            System.Drawing.Point clientPoint = _chart.PointToClient(new System.Drawing.Point((int)x, (int)y));

            int idxBar;
            int idxSection;

            if (_chart.GetBarSectionFromPoint(clientPoint, out idxBar, out idxSection))
            {
                ChartBarProvider barProvider = new ChartBarProvider(_chart, this, idxBar);

                // idxSection  is returned -1 if the point lies on a bar, but not on a section in the bar.
                if (idxSection == -1)
                {
                    return barProvider;
                }
                else
                {
                    // The BarSectionProvider must be aware of the bar it lies in, in order for
                    // UIA to be able to navigate up through the UIA tree from the Bar Section.
                    return new ChartBarSectionProvider(_chart, barProvider, idxBar, idxSection);
                }
            }

            return null;
        }

        /// <summary>
        /// Retrieves the element in this fragment that has the input focus.
        /// </summary>
        /// <returns>
        /// The provider for the element in this fragment that has the input focus, if any; otherwise, a null reference (Nothing in Visual Basic).
        /// </returns>
        IRawElementProviderFragment IRawElementProviderFragmentRoot.GetFocus()
        {
            return null;
        }

        #endregion


    }

The chart bar provider, this is one of the fragment.

public class ChartBarProvider : IRawElementProviderFragment, IValueProvider
    {
        private MyChart control;
        private int _idxBar;
        string strNameBar;
        protected IRawElementProviderFragment parent;
        protected IRawElementProviderFragmentRoot fragmentRoot;
        public ChartBarProvider(MyChart control, IRawElementProviderFragmentRoot root, int idxBar)
        {
            this.control = control;
            this._idxBar = idxBar;
            this.parent = root;
            this.fragmentRoot = root;
            strNameBar = control.GetNameFromBar(idxBar);
        }



        public System.Windows.Rect BoundingRectangle
        {
            get
            {
                System.Drawing.Rectangle screenRect = this.control.RectangleToScreen(this.control.RectFromBar(_idxBar));

                Rect result = new Rect();
                result.X = screenRect.X;
                result.Y = screenRect.Y;
                result.Width = screenRect.Width;
                result.Height = screenRect.Height;

                return result;
            }
        }

        public IRawElementProviderFragmentRoot FragmentRoot
        {
            get { return this.fragmentRoot; }
        }

        public IRawElementProviderSimple[] GetEmbeddedFragmentRoots()
        {
            return null;
        }

        public int[] GetRuntimeId()
        {
            int[] runtimeId = new int[2];

            runtimeId[0] = 3;
            runtimeId[1] = _idxBar;

            return runtimeId;
        }

        public IRawElementProviderFragment Navigate(NavigateDirection direction)
        {
            switch (direction)
            {
                case NavigateDirection.FirstChild:
                    {
                        int cSections = control.GetBarSectionCount(_idxBar);
                        if (cSections > 0)
                        {
                            return new ChartBarSectionProvider(this.control, this, _idxBar, 0);
                        }

                        return null;

                    }

                case NavigateDirection.LastChild:
                    {
                        // Return our last child, which is the last section in the bar.
                        int cSections = control.GetBarSectionCount(_idxBar);
                        if (cSections > 0)
                        {
                            return new ChartBarSectionProvider(this.control, this, _idxBar, Math.Max(cSections - 1, 0));
                        }
                        return null;
                    }

                case NavigateDirection.NextSibling:
                    {

                        // Return the fragment for the next bar in the chart.
                        if (_idxBar < control.GetBarCount() - 1)
                        {
                            return new ChartBarProvider(this.control, this.fragmentRoot, _idxBar + 1);
                        }
                        return null;
                    }

                case NavigateDirection.PreviousSibling:
                    {
                        // Return the fragment for the previous bar in the chart.
                        if (_idxBar > 0)
                        {
                            return new ChartBarProvider(this.control, this.fragmentRoot, _idxBar - 1);
                        }
                        return null;
                    }

                

            }

            return null;
        }

        public void SetFocus()
        {
            throw new NotImplementedException();
        }

        public object GetPatternProvider(int patternId)
        {
            if (patternId.Equals(ValuePatternIdentifiers.Pattern.Id))
            {
                return this;
            }
            else
            {
                return null;
            }
        }

        public object GetPropertyValue(int propertyId)
        {

            if (propertyId == AutomationElementIdentifiers.ControlTypeProperty.Id)
            {
                return ControlType.Custom.Id;
            }

            if (propertyId == AutomationElementIdentifiers.LocalizedControlTypeProperty.Id)
            {
                return "Chart Bar";
            }
            else if (propertyId == AutomationElementIdentifiers.NameProperty.Id)
            {
                return strNameBar;
            }
            else if (propertyId == AutomationElementIdentifiers.AutomationIdProperty.Id)
            {
                return "ChartBar" + strNameBar;
            }
            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return true;
            }

            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return false;
            }
            else if (propertyId == AutomationElementIdentifiers.IsKeyboardFocusableProperty.Id)
            {
                bool canFocus = false;
                return canFocus;
            }
            return null;
        }

        public IRawElementProviderSimple HostRawElementProvider
        {
            get { return null; }
        }

        public ProviderOptions ProviderOptions
        {
            get
            {
                return ProviderOptions.ServerSideProvider|ProviderOptions.UseComThreading;
            }
        }

        // IValuePattern members.
        public bool IsReadOnly
        {
            get { return true; }
        }

        public string Value
        {
            get { return control.GetValueFromBar(_idxBar).ToString(); }
        }

        public void SetValue(string value)
        {
            throw new InvalidOperationException("Chart bar is read-only.");
        }
    }

The chart section provider, this is another fragment

public class ChartBarSectionProvider : IRawElementProviderFragment, IValueProvider
    {
        private MyChart _chart;
        private int _idxBar;
        private int _idxSection;
        string strNameBar, strNameSection;
        protected IRawElementProviderFragment parent;
        protected IRawElementProviderFragmentRoot fragmentRoot;
        public ChartBarSectionProvider(MyChart chart, IRawElementProviderFragment parent, int idxBar, int idxSection)
        {
            this._chart = chart;
            this._idxBar = idxBar;
            this._idxSection = idxSection;
            strNameBar = _chart.GetNameFromBar(idxBar);
            strNameSection = _chart.GetNameFromBarSection(idxBar, idxSection);
            this.parent = parent;
            this.fragmentRoot = parent.FragmentRoot;
        }

        public Rect BoundingRectangle
        {
            get
            {
                System.Drawing.Rectangle screenRect = _chart.RectangleToScreen(_chart.RectFromSection(_idxBar, _idxSection));

                Rect result = new Rect();
                result.X = screenRect.X;
                result.Y = screenRect.Y;
                result.Width = screenRect.Width;
                result.Height = screenRect.Height;

                return result;
            }
        }

        public IRawElementProviderFragmentRoot FragmentRoot
        {
            get { return this.fragmentRoot; }
        }

        public IRawElementProviderSimple[] GetEmbeddedFragmentRoots()
        {
            return null;
        }

        public int[] GetRuntimeId()
        {
            int[] runtimeId = new int[3];

            runtimeId[0] = 3;
            runtimeId[1] = _idxBar;
            runtimeId[2] = _idxSection;

            return runtimeId;
        }

        public IRawElementProviderFragment Navigate(NavigateDirection direction)
        {
            switch (direction)
            {
                

                case NavigateDirection.NextSibling:
                    {

                        // Return the fragment for the next section in the bar.
                        int cSections = _chart.GetBarSectionCount(_idxBar);
                        if (_idxSection < cSections - 1)
                        {
                            return new ChartBarSectionProvider(_chart, this.parent, _idxBar, _idxSection + 1);
                        }
                        return null;
                    }

                case NavigateDirection.PreviousSibling:
                    {
                        // Return the fragment for the previous section in the bar.
                        int cSections = _chart.GetBarSectionCount(_idxBar);
                        if (cSections > 0)
                        {
                            return new ChartBarSectionProvider(_chart, this.parent, _idxBar, cSections - 1);
                        }
                        return null;
                    }



            }

            return null;
        }

        public void SetFocus()
        {
            throw new NotImplementedException();
        }

        public object GetPatternProvider(int patternId)
        {
            if (patternId.Equals(ValuePatternIdentifiers.Pattern.Id))
            {
                return this;
            }
            else
            {
                return null;
            }
        }

        public object GetPropertyValue(int propertyId)
        {
            if (propertyId == AutomationElementIdentifiers.ControlTypeProperty.Id)
            {
                return ControlType.Custom.Id;
            }

            if (propertyId == AutomationElementIdentifiers.LocalizedControlTypeProperty.Id)
            {
                return "Chart Bar Section";
            }
            else if (propertyId == AutomationElementIdentifiers.NameProperty.Id)
            {
                return strNameSection;
            }
            else if (propertyId == AutomationElementIdentifiers.AutomationIdProperty.Id)
            {
                return "ChartBar" + strNameBar + strNameSection;
            }
            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return true;
            }

            else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
            {
                return false;
            }
            else if (propertyId == AutomationElementIdentifiers.IsKeyboardFocusableProperty.Id)
            {
                bool canFocus = false;
                return canFocus;
            }
            return null;
        }

        public IRawElementProviderSimple HostRawElementProvider
        {
            get { return null; }
        }

        public ProviderOptions ProviderOptions
        {
            get { return ProviderOptions.ServerSideProvider | ProviderOptions.UseComThreading; }
        }

        // IValuePattern members.
        public bool IsReadOnly
        {
            get { return true; }
        }

        public string Value
        {
            get { return _chart.GetValueFromBarSection(_idxBar, _idxSection).ToString(); }
        }

        public void SetValue(string value)
        {
            throw new InvalidOperationException("Chart bar section is read-only.");
        }
    }
Advertisements

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