Press "Enter" to skip to content

WPF/C# – Generowanie menu przy użyciu mechanizmu refleksji

Tym razem coś o programowaniu, bo dawno nie było:)

Refleksja jest znana z tego, że jest wolna jak dupa węża. Ale lubię się ją bawić niemal tak samo, jak WMI.
Po prostu nadal mi to zionie magią. I sobie wymyśliłam, że spróbuję użyć tego mechanizmu do generowania menu „w locie”.


Postanowiłam, że każda klasa będzie menu, a jej podklasy będą funkcjami w danym menu. Całość wygląda następująco.

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace MenuGeneratorSample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            mainMenu.IsMainMenu = true;
            mainMenu.Items.Add(addClass(typeof(FirstClass)));
            mainMenu.Items.Add(addClass(typeof(SecondClass)));

        }
        public static object GetNewObject(Type t)
        {
            try
            {
                var constructorInfo = t.GetConstructor(new Type[] { });
                if (constructorInfo != null)
                    return constructorInfo.Invoke(new object[] { });
                else return null;
            }
            catch
            {
                return null;
            }
        }

        public MenuItem addClass(Type classType)
        {
            var MissingMethods = typeof (Object).GetMethods();
            List NotToAdd = new List();
            foreach (var method in MissingMethods)
            {
                NotToAdd.Add(method.Name);
            } //aby nie dodatwało ToStringów i innych gethashcode'ów
            MenuItem menu = new MenuItem();
            menu.Header = menu.Name = classType.Name;   
            var methods = classType.GetMethods();
            foreach (var method in methods)
            {
                if (NotToAdd.Contains(method.Name)) break;
                MenuItem item = new MenuItem();
                item.Header = item.Name = method.Name;
                item.Click += delegate(object sender, RoutedEventArgs args) { method.Invoke(GetNewObject(classType), null); };
                menu.Items.Add(item);
            }
            return menu;
        }

    }
}

Tą funkcję powyżej można przerobić, by zamiast typu pobierała jakiś obiekt. I wtedy zamiast classType.GetMethods() mielibyście obiekt.GetType().GetMethods();, a zamiast null jako 2 parametr invoka moglibyście dać tablicę obiektów (będącą częścią tego obiektu) która byłaby parametrami do danej funkcji.
Należałoby jeszcze przy definicji funkcji w nawiasie dodać ten obiekt jako parametr. Tutaj jednak założyłam najprostszy przypadek. Powyżej założyłam, że mainMenu to dodana do pliku xaml kontrolka typu menu. Aby przetestować tą funkcję należy dodać te dwie klasy (FirstClass i SecondClass). Mogą to być klasy które robią cokolwiek. Rozważając najprostszy przypadek:

using System.Windows;

namespace MenuGeneratorSample
{
    public class FirstClass
    {

        public void FirstMethod()
        {
            MessageBox.Show("Pierwsza metoda z klasy FirstClass");
        }

        public void SecondMethod()
        {
            MessageBox.Show("Druga metoda z klasy FirstClass");
        }
        public void ThirdMethod()
        {
            MessageBox.Show("Trzecia metoda z klasy FirstClass");
        }
    }
}

I jeszcze druga:

using System.Windows;

namespace MenuGeneratorSample
{
    public class SecondClass
    {
        public void FirstMethod()
        {
            MessageBox.Show("Pierwsza metoda z klasy SecondClass");
        }

        public void SecondMethod()
        {
            MessageBox.Show("Druga metoda z klasy SecondClass");
        }
    }
}

Foreach może się różnie zachowywać, więc może być lekki problem z zachowaniem kolejności między wersjami kompilatora (tak wynika z dokumentacji) ale to jest na prawdę pomijalne.

Jeżeli nie chcecie dodawać możecie dodać wszystkie typy z assembly (Assembly.GetTypes() i do tego jakiś foreach – zobaczyć co pominąć etc – pobawcie się sami).

Możliwości jest sporo- bawcie się dobrze;)