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
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.
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 🙂
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
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(, )