0

Been trying to wrap my head around delegates, but always seem to hit a brick wall when it comes to it sinking in.

I've tried to make my own example to help me understand, after reading an article about delegates -> http://www.codeproject.com/Articles/71154/C-Delegates-101-A-Practical-Example but so far have been unsucessful. The following code produces an error about this line: 'doneExecuting = FunctionListToRun(MainOfWindows);' = Unassigned local variable

Can someone tell me if I'm close and what I'm doing wrong?

(I've also included the UI code on pastebin in case it helps -> http://pastebin.com/D2BVZJXc)

public partial class MainWindow : Window
{
    public delegate bool FunctionToCall(MainWindow windowInstance);

    public MainWindow()
    {
        InitializeComponent();
        addMethodNames();
    }

    private void addMethodNames()
    {
        ListBoxItem lbi1 = new ListBoxItem();
        lbi1.Content = "Introduction"; lbi1.Name = "get" + lbi1.Content;
        listMethods.Items.Add(lbi1);

        ListBoxItem lbi2 = new ListBoxItem();
        lbi1.Content = "Greeting"; lbi2.Name = "get" + lbi2.Content;
        listMethods.Items.Add(lbi2);

        ListBoxItem lbi3 = new ListBoxItem();
        lbi3.Content = "Story"; lbi3.Name = "get" + lbi3.Content;
        listMethods.Items.Add(lbi3);

        ListBoxItem lbi4 = new ListBoxItem();
        lbi4.Content = "MyName"; lbi4.Name = "get" + lbi4.Content;
        listMethods.Items.Add(lbi4);

        ListBoxItem lbi6 = new ListBoxItem();
        lbi6.Content = "Conclusion"; lbi6.Name = "get" + lbi6.Content;
        listMethods.Items.Add(lbi6);
    }

    private void btnAddAction_Click(object sender, RoutedEventArgs e)
    {
        ListBoxItem lbi = (ListBoxItem)listMethods.Items[listMethods.SelectedIndex];
        listMethods.Items.Remove(lbi);
        listActions.Items.Add(lbi);
    }

    private void btnRemoveAction_Click(object sender, RoutedEventArgs e)
    {
        listActions.Items.RemoveAt(listActions.SelectedIndex);
    }

    private void btnRun_Click(object sender, RoutedEventArgs e)
    {
        bool doneExecuting = false;
        FunctionToCall FunctionListToRun;
        foreach (ListBoxItem methodName in listActions.Items)
        {
            Conclusion conc = new Conclusion();
            switch (methodName.Content.ToString())
            {
                case "Introduction":
                    Introduction intro = new Introduction();
                    FunctionListToRun = intro.getIntroduction;
                    break;
                case "Greeting":
                    Greeting greet = new Greeting();
                    FunctionListToRun = greet.getGreeting;
                    break;
                case "Story":
                    Story story = new Story();
                    FunctionListToRun = story.getStory;
                    break;
                case "MyName":
                    MyName name = new MyName();
                    FunctionListToRun = name.getName;
                    break;
                case "Conclusion":
                    FunctionListToRun = conc.getConclusion;
                    break;
                default:
                    FunctionListToRun = conc.getConclusion;
                    break;
            }
        }
        doneExecuting = FunctionListToRun(MainOfWindows);
    }
}

class Introduction
{
    public bool getIntroduction(MainWindow windowInstance)
    {
        windowInstance.txtResult.Text += " Hello there!";
        return true;
    }
}

class Greeting
{
    public bool getGreeting(MainWindow windowInstance)
    {
        windowInstance.txtResult.Text += " How are you today?";
        return true;
    }
}

class Story
{
    public bool getStory(MainWindow windowInstance)
    {
        windowInstance.txtResult.Text += " I once met a goat and his name was billy, and he lived on a plain that was very hilly.";
        return true;
    }
}

class MyName
{
    public bool getName(MainWindow windowInstance)
    {
        windowInstance.txtResult.Text += " My name is too infinity!";
        return true;
    }
}

class Conclusion
{
    public bool getConclusion(MainWindow windowInstance)
    {
        windowInstance.txtResult.Text += " That is all, goodbye!";
        return true;
    }
}
MikeDub
  • 5,143
  • 3
  • 27
  • 44
  • Dude, I have no idea what you're talking about, but your code is terrible. You must not create or manipulate UI elements in code in WPF. – Federico Berasategui Apr 29 '13 at 02:24
  • I would suggest reading this article it gives examples of delegate as well as using Action and a few other cool things to try.. http://msdn.microsoft.com/en-us/library/018hxwa8.aspx – MethodMan Apr 29 '13 at 02:28
  • _"You must not create or manipulate UI elements in code in WPF"_ Says who? – Chris Sinclair Apr 29 '13 at 02:56
  • @Chrissinclair common sense, I guess. Look at the code in my answer vs the horrible mess the OP posted. – Federico Berasategui Apr 29 '13 at 03:42
  • @HighCore I'm just saying that WPF != XAML. That there are perfectly good reasons for creating/manipulating UI elements in code. MVVM is great, and I totally agree it should be something to be followed, but I would not place the requirement that you _"must not"_ do anything _but_ MVVM or deviate from it even slightly no matter what. – Chris Sinclair Apr 29 '13 at 10:34
  • @ChrisSinclair Can you name a good valid reason to create UI elements in code rather than XAML? – Federico Berasategui Apr 29 '13 at 12:26
  • I do agree that it should be used the vast majority of the time. In my case: cross-platform, compile-time safe, high performance, dynamically generated at runtime UI code. Sometimes it's alright to just hammer something out small/prototype stuff (or especially in this case of this question, something _unrelated_ to MVVM and avoiding its scaffolding/complexities). – Chris Sinclair Apr 29 '13 at 12:43
  • @ChrisSinclair I disagree. What can be more cross-platform than MVVM? Still, If you need to generate UI "dynamically" at runtime (aside from the fact that there are `DataTemplates` for that), you could put together some XAML and use a XAMLReader. XAML-based technologies have a very different idea of "dynamic" (due again to DataTemplates) than ancient traditional UI frameworks. – Federico Berasategui Apr 29 '13 at 14:22

2 Answers2

3

I think you have the basic concepts of the delegate down and FWIW, I don't think your code is terrible:)

Getting past the Error: I think your delegate FunctionListToRun may never be assigned if listActions.Items is empty. In that case the foreach loop never executes any code inside it and FunctionListToRun never gets set to anything. So that is what is causing your "Unassigned local variable" error. Change the line

"FunctionToCall FunctionListToRun;"

to

"FunctionToCall FunctionListToRun = null".  

You should also check for null before you call the delegate

"doneExecuting = FunctionListToRun(MainOfWindows);"

Becomes:

if (null != FunctionListToRun)
    doneExecuting = FunctionListToRun(MainOfWindows);

This prevent you from getting a runtime nullreferenceexception in the case that listActions.Items is empty.

Matt Johnson
  • 1,913
  • 16
  • 23
  • Thanks Matt, even though highcore's answer was more helpful / enlightening over all, your answer directly solved the problem for which I posted, So I'll give you a point as well :) Cheers! – MikeDub Apr 30 '13 at 01:22
2

Ok, this is not a direct answer to your question, but rather a complete refactoring of your code the "right" way.

First thing you must understand when programming in WPF, is that UI is not Data and act accordingly.

That makes your following code:

 ListBoxItem lbi1 = new ListBoxItem();
 lbi1.Content = "Introduction"; lbi1.Name = "get" + lbi1.Content;
 listMethods.Items.Add(lbi1);
 //... etc boilerplate

completely irrelevant and undesired.

the ListBoxItems are not your concern. Your code should not use nor reference them. That's up to the UI to create the proper ListBoxItems given the proper data structures and Collections are provided.

This is the most important realization you must have when coming from traditional UI programming into MVVM-able XAML based frameworks.

Therefore, the first thing you must ALWAYS do when creating new UIs in WPF is to Create a proper Data Structure:

public class ActionItem
{
    public string DisplayName { get; set; }

    public Action Action { get; set; }
}

This is the data that will be represented by the ListBoxItems, with a DisplayName to be shown in the UI, and an Action delegate to execute.

As per the linked MSDN article, The System.Action delegate

Encapsulates a method that has no parameters and does not return a value.

Therefore it's perfectly suitable for our current needs. We need a reference to a method (that's what a delegate actually is) that takes no parameters and does not return anything, something like:

public void SimpleMethod()
{
    Result += "Simple Method!";
}

Also notice that C# supports the concept of Anonymous Methods and Lambda Expressions to actually write these kind of simple methods in shorter syntaxes.

For example, the above SimpleMethod() could be reduced to a Lambda Expression like:

() => Result += "Simple Method!";

This removes the need to declare an additional identifier (the method name) and simplifies and helps keep code clean.

Going back to our example, the second thing you need when creating WPF UIs is the ViewModel, this class actually represents (and holds) the data that will be shown on screen:

public class ActionsViewModel: PropertyChangedBase
{
    public ObservableCollection<ActionItem> AvailableActions { get; set; } 

    public ObservableCollection<ActionItem> SelectedActions { get; set; } 

    public ActionItem FocusedAction1 { get; set; }
    public ActionItem FocusedAction2 { get; set; }

    private string _result;
    public string Result
    {
        get { return _result; }
        set
        {
            _result = value;
            OnPropertyChanged("Result");
        }
    }

    public ActionsViewModel()
    {
        AvailableActions = new ObservableCollection<ActionItem>
                                {
                                    new ActionItem() {DisplayName = "Introduction", Action = () => Result += " Hello there!"},
                                    new ActionItem() {DisplayName = "Greeting", Action = () => Result += " How are you today?"},
                                    new ActionItem() {DisplayName = "Story", Action = () => Result += " I once met a goat and his name was billy, and he lived on a plain that was very hilly."},
                                    new ActionItem() {DisplayName = "My Name", Action = () => Result += "My name is too infinity!"},
                                    new ActionItem() {DisplayName = "Conclusion", Action = () => Result += "That is all, goodbye!"}
                                };

        SelectedActions = new ObservableCollection<ActionItem>();
    }

    public void AddAction()
    {
        var focused = FocusedAction1;

        if (focused != null)
        {
            AvailableActions.Remove(focused);
            SelectedActions.Add(focused);
        }
    }

    public void DeleteAction()
    {
        var focused = FocusedAction2;

        if (focused != null)
        {
            SelectedActions.Remove(focused);
            AvailableActions.Add(focused);
        }
    }

    public void Run()
    {
        Result = string.Empty;
        SelectedActions.ToList().ForEach(x => x.Action());
    }
}

Notice that this class has no interaction (nor reference) to any UI element. In MVVM (the preferred and easier approach to WPF), the Application Logic and Data must be completely decoupled from the UI. This enables a high level of customizability to both parts without too much interdependence of one another.

Also notice that I'm defining 2 ObservableCollection<ActionItem>, these are the ones that will be represented on-screen by the 2 ListBoxes, Whereas FocusedAction1 and FocusedAction2 represent the highlighted item in each ListBox, and finally a string Result property that will be used to store the results.

Also Notice that in order to support Two Way DataBinding, the ViewModel classes must implement the INotifyPropertyChanged interface, therefore our ViewModel is derived from a PropertyChangedBase class that looks like this:

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        Application.Current.Dispatcher.BeginInvoke((Action) (() =>
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
            }));
    }
}

Next, we can proceed to actually define our UI:

<Window x:Class="MiscSamples.ActionsListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ActionsListBox" Height="600" Width="1000">
    <DockPanel>
        <StackPanel Orientation="Horizontal" 
                    DockPanel.Dock="Top">
            <Button Margin="2" Content="Add Action" Click="AddAction" />
            <Button Margin="2" Content="Delete Action" Click="DeleteAction" />
            <Button Margin="2" Content="Run" Click="Run"/>
        </StackPanel>

        <TextBox Text="{Binding Result}" DockPanel.Dock="Bottom" Height="28" IsReadOnly="True"/>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <ListBox ItemsSource="{Binding AvailableActions}" 
                     SelectedItem="{Binding FocusedAction1}"
                     DisplayMemberPath="DisplayName"/>

            <ListBox ItemsSource="{Binding SelectedActions}" 
                     SelectedItem="{Binding FocusedAction2}"
                     DisplayMemberPath="DisplayName"
                     Grid.Column="1"/>
        </Grid>
    </DockPanel>
</Window>

Notice that I've not named ANY elements in XAML. This helps a LOT when you need to get used to the MVVM mentality. The inability to actually manipulate any UI element from code behind makes you rethink your approach every time you feel the temptation of doing so.

Again, Im making extensive use of DataBinding to connect the UI to the ViewModel, that's why the need to manipulate the UI in code is eliminated.

You may have noticed another very important aspect of this approach: the reduction of boilerplate to almost zero. There's no casting stuff, no ToString() no nothing of that, just simple, simple properties and DataBinding, That's how you develop in WPF.

Finally, the Code Behind, and some event handlers. I usually prefer to use Commands rather than Click handlers for Buttons, but for the simplicity of this sample, I'll stick to the traditional approach:

public partial class ActionsListBoxSample : Window
{
    public ActionsViewModel ViewModel { get; set; }

    public ActionsListBoxSample()
    {
        InitializeComponent();

        DataContext = ViewModel = new ActionsViewModel();
    }

     private void AddAction(object sender, RoutedEventArgs e)
    {
        ViewModel.AddAction();
    }

    private void DeleteAction(object sender, RoutedEventArgs e)
    {
        ViewModel.DeleteAction();
    }

    private void Run(object sender, RoutedEventArgs e)
    {
        ViewModel.Run();
    }
}

Notice how the Click handlers are merely reduced to executing logic in the ViewModel. That's a key concept, you must NEVER place application logic in the Code Behind.

All this gives the following result:

enter image description here

Notice that when clicking the Run Button, all the Actions in the right side are executed in order. Our job is done. No switch, no casting, no ListBoxItem. anything, no complicated visual tree manipulation stuff. Much more maintainable, scalable and beautiful code. That's what WPF and MVVM help to produce.

Edit: Adding a sample with Commands, as per the OP's request:

Commands in WPF serve as abstractions of "user actions" (not only Button Clicks), for example, a KeyGesture can be associated with a Command:

<TextBox>
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding SomeCommand}"/>
    </TextBox.InputBindings>
</TextBox>

Also, many "clickable" UI elements (MenuItem, Button, CheckBox, ToggleButton) already have a Command property that you can bind to some ICommand implementation in the ViewModel.

This makes it very easy to wire up the UI elements to some behavior defined in the ViewModel. Again, the main goal here is to separate the UI elements and their events from the actual implementing code.

Here is the simplest Reusable ICommand implementation I've been able to come up with:

    //Dead-simple implementation of ICommand
    //Serves as an abstraction of Actions performed by the user via interaction with the UI (for instance, Button Click)
    public class Command : ICommand
    {
        public Action Action { get; set; }

        public void Execute(object parameter)
        {
            if (Action != null)
                Action();
        }

        public bool CanExecute(object parameter)
        {
            return IsEnabled;
        }

        private bool _isEnabled = true;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                _isEnabled = value;
                if (CanExecuteChanged != null)
                    CanExecuteChanged(this, EventArgs.Empty);
            }
        }

        public event EventHandler CanExecuteChanged;

        public Command(Action action)
        {
            Action = action;
        }
    }

So, we could now declare some properties of this Type in our existing ActionsViewModel:

public Command AddActionCommand { get; set; }
public Command DeleteActionCommand { get; set; }
public Command RunCommand { get; set; }

and instantiate them in the constructor:

public ActionsViewModel()
{
   //Existing code here

    AddActionCommand = new Command(AddAction);
    DeleteActionCommand = new Command(DeleteAction);
    RunCommand = new Command(Run);
}

Notice that the Command takes an Action parameter in the constructor (again, a Reference to a Method), which will be executed when the command is invoked.

So, we now replace the Click handlers referenced in XAML for these commands:

 <Button Margin="2" Content="Add Action" Command="{Binding AddActionCommand}" />
 <Button Margin="2" Content="Delete Action" Command="{Binding DeleteActionCommand}" />
 <Button Margin="2" Content="Run" Command="{Binding RunCommand}"/>

and then remove the Click handlers no longer needed from Code Behind, and since we also removed almost all code from there, we no longer need to even have a reference to the ViewModel anymore:

public partial class ActionsListBoxSample : Window
{
    public ActionsListBoxSample()
    {
        InitializeComponent();
        DataContext = new ActionsViewModel();
    }
}

The result is exactly the same as before, but we have now moved even more stuff from the Code Behind into the ViewModel. This is just a simple example, but there are more complex types of Commands, such as Prism's DelegateCommand<T>

It's also important to mention that if the CanExecute() method of the Command evaluates to false after the CanExecuteChanged event is raised, all relevant UI elements (I.E elements for which the Command property is bound / set to the Command in question), these UI elements are actually automatically Disabled. This comes really handy when you think about it, as there might be many UI elements bound to the same command(s) in the ViewModel, and you don't have to manage these UI elements individually.

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • Thanks HighCore, I've been meaning to learn design patterns such as MVVM, but figured i needed to wrap my head around concepts such as Delegates, Anonymous Methods and Lambda Expressions first, which is what I've created this example quickly for. I appreciate your input, I will run through your example later tonight when I have time and get back to you on how I went, thanks again for your help :) – MikeDub Apr 29 '13 at 05:03
  • Thanks again, I've just managed to go through the whole thing and there are alot of lessons in there that I thank you with, Interfaces, Change notification / data binding, Delegates, Lambdas / Annoymous Methods, MVVM / Code structure, all things i needed to brush up on. I spose I'll have to think of another scenario and try coding it from scratch with this new knowledge to see how I go! – MikeDub Apr 30 '13 at 00:46
  • HighCore, you've made mention that you prefer commands, would you be able to provide an example of how you would do that with this project? – MikeDub May 01 '13 at 02:28