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

    Question

  • 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
    Sunday, September 11, 2011 1:03 PM

Answers

  • 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

    Friday, September 30, 2011 11:12 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

                }

            }

     

        }

    Tuesday, October 04, 2011 7:36 PM

All replies

  • 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 5:55 PM
  • 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 8:05 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

    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

    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 7:42 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: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

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

    paul van bladel
    Monday, September 26, 2011 8:46 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


    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.co

    So, it seems not to be possilbe.

     


    paul van bladel
    Tuesday, September 27, 2011 4:25 AM
  • 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

    Friday, September 30, 2011 11:12 PM
  • Wow Diego... brillant !

    thx

    paul.


    paul van bladel
    Saturday, October 01, 2011 6:15 AM
  • 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.

    Saturday, October 01, 2011 7:24 AM
  • 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 12:25 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
    Sunday, October 02, 2011 5:00 PM
  • 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 4:05 PM
  • Very inventive Diego !


    paul van bladel
    Monday, October 03, 2011 6:42 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

                }

            }

     

        }

    Tuesday, October 04, 2011 7:36 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 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

                }

            }

     

        }


    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:14 PM
  • Thank you Bilal, lightswitch a new world to explore!
    Tuesday, October 04, 2011 8:48 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
    Tuesday, October 04, 2011 9:20 PM
  • To keep strong typing, it would even be better to use:

    var model = Details.GetModel();
    var rootContent = model.RootContentItem;
    var rootControl = this.FindControl(rootContent.Name);
    Thursday, June 20, 2013 2:48 PM
  • Hi guys!

    I've been learning LightSwitch recently and first of all I'd like to thank you all for your great posts and blog entries about it.

    Regarding the question - if you'd like to stick to the this.FindControl() method approach, then you CAN enumerate over controls as follows:

    public static class ScreenObjectExtensions { public static IEnumerable<IContentItemProxy> FindControls(this IScreenObject screen) { var model = screen.Details.GetModel(); return model.GetChildItems() .SelectManyRecursive(c => c.GetChildItems()) // recursion, but screens are not that complex .OfType<IContentItemDefinition>() .Select(c => screen.FindControl(c.Name)); } } public static class IEnumerableExtensions { public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector) { foreach (T item in source) { yield return item;

    var children = selector(item);
    if (children == null)
    continue;

    foreach (T descendant in children.SelectManyRecursive(selector))

    { yield return descendant; } } } }

    And example of use in the screen code:

    foreach (var control in this.FindControls())
    {
        control.IsReadOnly = true;
    }


    • Edited by Patryk Marchwicki Friday, June 21, 2013 9:36 AM A null-check in SelectManyRecursive would be useful
    Thursday, June 20, 2013 3:02 PM
  • Thanks for sharing Patryk. I love the yield statement !

    paul van bladel

    Thursday, June 20, 2013 9:39 PM
  • Hi guys,

    Where in the project do we put Patryks code so that FindControls is available to a screen?

    TIA,

    Josh

    Thursday, January 16, 2014 5:29 PM
  • Hey Josh,

    you can put it anywhere in the silverlight client project and it will be accessible to your screen.

    Best of luck!

    Jan


    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Thursday, January 16, 2014 7:32 PM
  • Sorry, I'm new to Lightswitch and need more explanation about "anywhere".  I assumed you meant I could add it to an existing method.  I tried just that in the Create method of my detail screen.  I got a number of errors including one that said it must be in a module. 

    So I tried to create my own method (Solution Explorer...File View...Client...Add Module) but still get the errors.


    Thanks.

    Thursday, January 16, 2014 8:17 PM
  • In Solution Explorer Right Click on Desktop Client Project then choose

    Add | New | Module

    Name it ScreenObjectExtensions

    Paste the following into the file ScreenObjectExtensions.vb

    Namespace LightSwitchApplication Module ScreenObjectExtensions

    <system.runtime.compilerservices.extension()> Public Function FindControls(screen As Microsoft.LightSwitch.Client.IScreenObject) As IEnumerable(Of IContentItemProxy) Dim model = screen.Details.GetModel() ' recursion, but screens are not that complex Return model.GetChildItems().SelectManyRecursive(Function(c) c.GetChildItems()).OfType(Of Microsoft.LightSwitch.Model.IContentItemDefinition)().[Select](Function(c) screen.FindControl(c.Name)) End Function <system.runtime.compilerservices.extension()> Public Iterator Function SelectManyRecursive(Of T)(source As IEnumerable(Of T), selector As Func(Of T, IEnumerable(Of T))) As IEnumerable(Of T) For Each item As T In source Yield item Dim children = selector(item) If children Is Nothing Then Continue For End If For Each descendant As T In children.SelectManyRecursive(selector) Yield descendant Next Next End Function End Module End Namespace

    Now in your screen code you can do this:

    Private Sub someScreen_Activated() For Each control In Me.FindControls() AddHandler control.ControlAvailable, AddressOf TextBoxAvailable Next End Sub Private Sub TextBoxAvailable(sender As Object, e As ControlAvailableEventArgs) On Error Resume Next

    AddHandler CType(e.Control, System.Windows.Controls.TextBox).GotFocus, AddressOf TextBoxKeyUp End Sub Private Sub TextBoxKeyUp(sender As Object, e As System.Windows.RoutedEventArgs) CType(sender, System.Windows.Controls.Control).Background = New System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Cyan) End Sub


    HTH,

    Josh




    • Edited by joshbooker Friday, January 17, 2014 2:41 AM
    Thursday, January 16, 2014 8:32 PM
  • I inserted the code here:  (sorry, I said Create method earlier)

    Namespace LightSwitchApplication
    
        Public Class tblClientDemographicDetail
    
            Private Sub tblClientDemographic_Loaded(succeeded As Boolean)
                ' Write your code here.
                Me.SetDisplayNameFromEntity(Me.tblClientDemographic)
            End Sub
    
            Private Sub tblClientDemographic_Changed()
                ' Write your code here.
                Me.SetDisplayNameFromEntity(Me.tblClientDemographic)
            End Sub
    
            Private Sub tblClientDemographicDetail_Saved()
                ' Write your code here.
                Me.SetDisplayNameFromEntity(Me.tblClientDemographic)
            End Sub
    
        End Class
    
        Public NotInheritable Class ScreenObjectExtensions
            Private Sub New()
            End Sub
            <System.Runtime.CompilerServices.Extension()> _
            Public Shared Function FindControls(screen As IScreenObject) As IEnumerable(Of IContentItemProxy)
                Dim model = screen.Details.GetModel()
    
                ' recursion, but screens are not that complex
                Return model.GetChildItems().SelectManyRecursive(Function(c) c.GetChildItems()).OfType(Of IContentItemDefinition)().[Select](Function(c) screen.FindControl(c.Name))
            End Function
        End Class
    
        Public NotInheritable Class IEnumerableExtensions
            Private Sub New()
            End Sub
            <System.Runtime.CompilerServices.Extension()> _
            Public Shared Function SelectManyRecursive(Of T)(source As IEnumerable(Of T), selector As Func(Of T, IEnumerable(Of T))) As IEnumerable(Of T)
                For Each item As T In source
    			yield Return item
    
                    Dim children = selector(item)
                    If children Is Nothing Then
                        Continue For
                    End If
    
                    For Each descendant As T In children.SelectManyRecursive(selector)
    				yield Return descendant
                    Next
                Next
            End Function
        End Class

    I added the namespaces the code wanted.  I can't get past the other errors (shown below)


    Thanks.


    • Edited by db_dweeb Thursday, January 16, 2014 9:27 PM
    Thursday, January 16, 2014 9:26 PM
  • EManning,

    The c# to vb converter resulted in some error prone code. Plus you need to put the code in a new module.

    I debugged the code and edited my post above.  Please try again.

    Have a great day!

    Josh


    • Edited by joshbooker Friday, January 17, 2014 2:43 AM
    Friday, January 17, 2014 2:39 AM