Is there a way to programatically browse all controls on a screen?

已答复 Is there a way to programatically browse all controls on a screen?

  • Sunday, September 11, 2011 1:03 PM
     
     

    Since Lightswitch is based on MVVM, screen control events are "less" important. Commands, on the other hand, are first-class citizens.

    Nonetheless, once in a while there is a need for attaching an event to a specific control. This can be done via the this.FindControl ("MyControl") method. This is very powerful, but it's usage escapes compile time checking, because of the "magic strings" (e.g. "MyControl"). What can go wrong? Well, renaming controls without updating the MyControl method param!

    I'm currently looking for a way to foresee "something" that can improve this issue.  If finished I will be very happy to share it with the LightSwitch community. But first I have to overcome the following: How can I programmatically "browse" all conttrols on a specific screen? Is there, anyhow,  an API for this? 

     


    paul van bladel

All Replies

  • Wednesday, September 21, 2011 5:55 PM
     
     

    I'll answer myself: programmaticaly browsing all controls on a screen is NOT possible. (NOT TRUE !!!!)

    Thanks to Diego we know better now :)

    paul.


    paul van bladel
  • Wednesday, September 21, 2011 8:05 PM
    Moderator
     
     

    What makes you say that it's not possible Paul?

    If you mean looping through the controls on a screen programatically & examining/acting on them, it *is* possible.

    You just need to start from the root control, which if I remember correct is RootVisual. It's RootSomething, if it's not RootVisual, I can't find the post right now where the name was figured out, but I remember it.


    Yann

    (plus ça change, plus c'est la même chose!)

    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, remember to "Mark as Answer".

    This will help people find the answers that they're looking for more quickly.

  • Wednesday, September 21, 2011 11:23 PM
     
     

    You are right that LightSwitch uses MVVM.

    What Lightswitch allows is the ability to access everything on the screen. The properties and collections. This is what you want to manipulate. See:

    A Random Walk Through The LightSwitch Data Model

    image


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com

  • Monday, September 26, 2011 7:42 PM
     
     

    You are right that LightSwitch uses MVVM.

    What Lightswitch allows is the ability to access everything on the screen. The properties and collections. This is what you want to manipulate. See:

    A Random Walk Through The LightSwitch Data Model

    image


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com

    Michael,

    I guess you are referring to:

      var x = this.Details.Screen.Details.Properties.All();

    from your article.
    That's giving me the properties of the viewModel, but not of the screen (or view) itself.
    I need to be able to browse the controls on a screen, not the collections on the viewModel.


    paul van bladel
  • Monday, September 26, 2011 8:31 PM
     
     

    You should be able to get to the actual screen using:

    this.Details.Screen


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com

  • Monday, September 26, 2011 8:46 PM
     
     

    You should be able to get to the actual screen using:

    this.Details.Screen


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com

    Sure, but how can I programmatically "browse" (iterate over) all conttrols on a specific screen?

    paul van bladel
  • Monday, September 26, 2011 9:08 PM
     
     

    You should be able to cast it to:

    < strike >Microsoft.LightSwitch.Client.IVisualCollection< /strike > <- (No I am wrong)

    (see: http://tejana.com/Home/Default.aspx?tabid=71)

    However, I do not reccomend this. You have access to the Property the controls are bound to, you want to modify the property not the control. You modify the property and the control will automatically be updated. This is the principal of the MVVM design.

     


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com


  • Tuesday, September 27, 2011 4:25 AM
     
     

    You should be able to cast it to:

    < strike >Microsoft.LightSwitch.Client.IVisualCollection< /strike > <- (No I am wrong)

    (see: http://tejana.com/Home/Default.aspx?tabid=71)

    However, I do not reccomend this. You have access to the Property the controls are bound to, you want to modify the property not the control. You modify the property and the control will automatically be updated. This is the principal of the MVVM design.

     


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.co

    So, it seems not to be possilbe.

     


    paul van bladel
  • Friday, September 30, 2011 11:12 PM
     
     Answered Has Code

    Hello Paul, hello adef, hello Yann I'm back, good to see you again like beta days!

    My answer is yes! You can programmatically browsing all controls on a screen.

    The idea is to get a proxy for a control in the screen (I test with a TextBox or DataGrid) and get the DataContext, a Microsoft.LightSwitch.Presentation.IContentItem, and go back in the hierarchy of IContentItem to the screen root. Once I get the root item I can visit every control in the screen:

             partial void MyScreen_Created()
             {
                var proxy = this.FindControl("KnowControl");
    
                proxy.ControlAvailable += (s, e) =>
                {
                    var ctrl = e.Control as System.Windows.Controls.Control;
    
                    var contentItem = ctrl.DataContext as Microsoft.LightSwitch.Presentation.IContentItem;
    
                    //Find the Screen Root
                    var parent = contentItem.EffectiveParentItem;
                    while (parent.EffectiveParentItem != null)
                        parent = parent.EffectiveParentItem;
    
                    //Visit the control hierarchy
                    VisitControl(parent);      
                };
            }
    
            public void VisitControl(Microsoft.LightSwitch.Presentation.IContentItem item)
            {
                var c = this.FindControl(item.Name);
    
                if (c != null)
                    System.Diagnostics.Debug.WriteLine("Control: " + item.Name + ", DisplayName:" + c.DisplayName);
                else
                    System.Diagnostics.Debug.WriteLine("Control not found: " + item.Name);
    
                foreach (var i in item.ChildItems)           
                    VisitControl(i);
            }
    


    Hope this help! bye

  • Saturday, October 01, 2011 6:15 AM
     
     

    Wow Diego... brillant !

    thx

    paul.


    paul van bladel
  • Saturday, October 01, 2011 7:24 AM
    Moderator
     
      Has Code

    Welcome back Diego!!!

    This is a good method, but it's more complicated than it needs to be. I just wish I could find the post where I worked this out with Garth (back in the B2 forum).

    Doesn't

    var proxy = this.FindControl("RootLayout");
    

    work?

     (I said "RootVisual" before, but I think I just remembered it's "RootLayout")

    Yann

    (plus ça change, plus c'est la même chose!)

    If you found this post helpful, please "Vote as Helpful". If it actually answered your question, remember to "Mark as Answer".

    This will help people find the answers that they're looking for more quickly.

  • Sunday, October 02, 2011 12:25 PM
     
      Has Code

    Hello Paul, hello adef, hello Yann I'm back, good to see you again like beta days!

    My answer is yes! You can programmatically browsing all controls on a screen.

    The idea is to get a proxy for a control in the screen (I test with a TextBox or DataGrid) and get the DataContext, a Microsoft.LightSwitch.Presentation.IContentItem, and go back in the hierarchy of IContentItem to the screen root. Once I get the root item I can visit every control in the screen:

             partial void MyScreen_Created()
             {
                var proxy = this.FindControl("KnowControl");
    
                proxy.ControlAvailable += (s, e) =>
                {
                    var ctrl = e.Control as System.Windows.Controls.Control;
    
                    var contentItem = ctrl.DataContext as Microsoft.LightSwitch.Presentation.IContentItem;
    
                    //Find the Screen Root
                    var parent = contentItem.EffectiveParentItem;
                    while (parent.EffectiveParentItem != null)
                        parent = parent.EffectiveParentItem;
    
                    //Visit the control hierarchy
                    VisitControl(parent);      
                };
            }
    
            public void VisitControl(Microsoft.LightSwitch.Presentation.IContentItem item)
            {
                var c = this.FindControl(item.Name);
    
                if (c != null)
                    System.Diagnostics.Debug.WriteLine("Control: " + item.Name + ", DisplayName:" + c.DisplayName);
                else
                    System.Diagnostics.Debug.WriteLine("Control not found: " + item.Name);
    
                foreach (var i in item.ChildItems)           
                    VisitControl(i);
            }
    


    Hope this help! bye

    Hi Diego,

     

    Is it correct that the code could be simplified by "assuming" that every screen starts with "RootContentItem" ?

    It works in a few screen I tested, but do you think it's a reasonable assumption?

    If yes, your code becomes:

    public void BrowseControlsOnScreen(IScreenObject myScreen)
            {
                StringBuilder stringBuilder = new StringBuilder();
    
                IContentItemProxy proxy = myScreen.FindControl("RootContentItem");
    
                proxy.ControlAvailable += (s, e) =>
                {
                    var ctrl = e.Control as System.Windows.Controls.Control;
    
                    IContentItem contentItem = ctrl.DataContext as Microsoft.LightSwitch.Presentation.IContentItem;
    
                    VisitControl(myScreen, contentItem, stringBuilder);
    
                    string result = stringBuilder.ToString();
                    this.ShowMessageBox(result);
                };
            }
    
            public void VisitControl(IScreenObject screen, Microsoft.LightSwitch.Presentation.IContentItem item, StringBuilder stringBuilder)
            {
                var c = screen.FindControl(item.Name);
    
                if (c != null)
                {
                    System.Diagnostics.Debug.WriteLine("Control: " + item.Name + ", DisplayName:" + c.DisplayName);
                    stringBuilder.AppendLine("Control: " + item.Name + ", DisplayName:" + c.DisplayName);
                }
                else
                    System.Diagnostics.Debug.WriteLine("Control not found: " + item.Name);
    
                foreach (var i in item.ChildItems)
                    VisitControl(screen, i, stringBuilder);
            }
    

    I have another question. Is there a way to "browse" all screens in your app. Not only the active screen (because that can be easily done by Application.ActiveScreens....) ? Any idea?

    Thanks

    paul


    paul van bladel
  • Sunday, October 02, 2011 5:00 PM
     
     
    Hello Paul, about browsing all screens in the app, my first thought was to use reflection, get all the classes within the assembly and all the classes that implement the IScreenObject interface are Screens!

    About your code fix it looks good, before posting my code, I tried getting a reference to the "ScreenLayout" control, but that control doesn't seem to trigger the ControlAvariable event, if "RootContentItem" triggers the ControlAvariable everything will be ok, Yann suggested something like this :)

    Paul, you give me another idea about getting all the screens, and all the controls withing the screens, all the lightswitch magic lives in the ApplicationDefinition.lsml, if we are able to parse that xml file we could get all the app information, tomorrow I'll make some test and I'll tell you the the results, actually my phone doesn't run visual studio :p

    Bye
  • Monday, October 03, 2011 4:05 PM
     
      Has Code

    Hello, parsing the ApplicationDefinition.lsml file you can get the screens and controls information and more! Here is another version of getting all the screens and visiting all the controls in the screen:
     

            public static void ListAllScreensAndControls()
            {
                XDocument doc = XDocument.Load("ApplicationDefinition.lsml");
    
                //Get all the screens
                var screens = doc.Root.Elements("{http://schemas.microsoft.com/LightSwitch/2010/xaml/model}Screen");
    
                foreach (var s in screens)
                {
                    System.Diagnostics.Debug.WriteLine("Screen: " + s.Attribute("Name").Value);
    
                    //Get al the controls in the screen
                    var items = s.Descendants("{http://schemas.microsoft.com/LightSwitch/2010/xaml/model}ContentItem");
    
                    foreach(var i in items)
                        System.Diagnostics.Debug.WriteLine("\tName: " + i.Attribute("Name").Value + ", " + i.Attribute("Kind").Value);
                }
            }
    
            public static void VisitControls(IScreenObject screen) 
            {
                XDocument doc = XDocument.Load("ApplicationDefinition.lsml");
    
                //Get the screen
                var screenNode = doc.Root.Elements("{http://schemas.microsoft.com/LightSwitch/2010/xaml/model}Screen").Where(s => s.Attribute("Name").Value == screen.GetType().Name).FirstOrDefault();
    
                if (screenNode == null)
                    throw new Exception("Screen not found");
    
                //Get the controls in the screen
                foreach (var i in screenNode.Descendants("{http://schemas.microsoft.com/LightSwitch/2010/xaml/model}ContentItem"))
                {
                    var itemName = i.Attribute("Name").Value;
                    var kindName = i.Attribute("Kind").Value;
    
                    try
                    {
                        var c = screen.FindControl(itemName);
                        System.Diagnostics.Debug.WriteLine("Control: " + itemName + ", DisplayName:" + c.DisplayName);                                           
                    }
                    catch(Exception ex) 
                    {
                        //Summary controls could have subcontrols
                        System.Diagnostics.Debug.WriteLine("Control not found: " + itemName + ", Kind: " + kindName);
                    }
                }
                
            }
    

    Hope this help, bye!
     

  • Monday, October 03, 2011 6:42 PM
     
     

    Very inventive Diego !


    paul van bladel
  • Tuesday, October 04, 2011 7:36 PM
     
     Answered

    If something is not available through runtime objects and you really need to introspect model then the right way is to use model API instead of parsing lsml file.

     

    The API is available under Microsoft.LightSwitch.Model namespace e.g. you may write something like this to get all screen definitions.

     

        public partial class Application

        {

            partial void Application_Initialize()

            {

                IApplicationDefinition appDef =  this.Details.GetModel();

                foreach (var screen in appDef.GlobalItems.OfType<IScreenDefinition>())

                {

                    // write your code here for each screen definition

                }

            }

     

        }

  • Tuesday, October 04, 2011 7:46 PM
     
     

    If something is not available through runtime objects and you really need to introspect model then the right way is to use model API instead of parsing lsml file.

     

    The API is available under Microsoft.LightSwitch.Model namespace e.g. you may write something like this to get all screen definitions.

     

        public partial class Application

        {

            partial void Application_Initialize()

            {

                IApplicationDefinition appDef =  this.Details.GetModel();

                foreach (var screen in appDef.GlobalItems.OfType<IScreenDefinition>())

                {

                    // write your code here for each screen definition

                }

            }

     

        }

    I'm impressed!

    Lightswitch = simple/transparant on the outside, but rich on the inside. That's clear.

    thx

    paul.


    paul van bladel
  • Tuesday, October 04, 2011 8:14 PM
     
     

    If something is not available through runtime objects and you really need to introspect model then the right way is to use model API instead of parsing lsml file.

     

    The API is available under Microsoft.LightSwitch.Model namespace e.g. you may write something like this to get all screen definitions.

     

        public partial class Application

        {

            partial void Application_Initialize()

            {

                IApplicationDefinition appDef =  this.Details.GetModel();

                foreach (var screen in appDef.GlobalItems.OfType<IScreenDefinition>())

                {

                    // write your code here for each screen definition

                }

            }

     

        }


    Thank you for posting this.

    It opens up a lot of things.


    Make Them Ask: That's a LightSwitch App?

    http://LightSwitchHelpWebsite.com

  • Tuesday, October 04, 2011 8:48 PM
     
     
    Thank you Bilal, lightswitch a new world to explore!
  • Tuesday, October 04, 2011 9:20 PM
     
     

    Allow me to refer back to my initial reason why I was in search for a robust way to browse all controls on all screens:

    "...Nonetheless, once in a while there is a need for attaching an event to a specific control. This can be done via the this.FindControl ("MyControl") method. This is very powerful, but it's usage escapes compile time checking, because of the "magic strings" (e.g. "MyControl"). What can go wrong? Well, renaming controls without updating the MyControl method param!

    I'm currently looking for a way to foresee "something" that can improve this issue.  If finished I will be very happy to share it with the LightSwitch community. But first I have to overcome the following: How can I programmatically "browse" all conttrols on a specific screen? Is there, anyhow,  an API for thiis?

    -------

    With the help of Diego and Bilal, this is now a piece of cake. It should be possible to include a resource file in the app, and refer in  the this.FindControl method to a particular entry in the resource file. Next, in the app initialize I can iterate over the resource file and check if the controls are really present in the model.  I could surround this "check" with an #IFDEBUG, so there is no impact on production code. I will never be faced with a non-working app because of accidentally renamed controls.

    But, more important, I would suspect (under the assumption we can access and set the display value prop) that the approach of Bilal, opens the door for MULTI LANGUAGE APPS !!!!


    paul van bladel