0

一直试图把我的头绕在代表身上,但当它陷入困境时似乎总是撞到一堵砖墙。

在阅读了一篇关于代表的文章后,我尝试制作自己的示例来帮助我理解 - > http://www.codeproject.com/Articles/71154/C-Delegates-101-A-Practical-Example 但到目前为止一直不成功。下面的代码产生关于这一行的错误:'doneExecuting = FunctionListToRun(MainOfWindows);' = 未分配的局部变量

有人可以告诉我我是否接近以及我做错了什么?

(我还在 pastebin 上包含了 UI 代码,以防它有帮助 -> 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;
    }
}
4

2 回答 2

3

我认为你有委托和 FWIW 的基本概念,我不认为你的代码很糟糕:)

克服错误:如果 listActions.Items 为空,我认为您的委托 FunctionListToRun 可能永远不会被分配。在这种情况下,foreach 循环永远不会执行其中的任何代码,并且 FunctionListToRun 永远不会被设置为任何内容。这就是导致“未分配的局部变量”错误的原因。换行

"FunctionToCall FunctionListToRun;"

"FunctionToCall FunctionListToRun = null".  

您还应该在调用委托之前检查 null

"doneExecuting = FunctionListToRun(MainOfWindows);"

变成:

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

这可以防止您在 listActions.Items 为空的情况下获得运行时 nullreferenceexception。

于 2013-04-29T03:31:06.227 回答
2

好的,这不是对您问题的直接回答,而是以“正确”方式对代码进行完整重构。

在 WPF 中编程时,您必须了解的第一件事是UI 不是数据并采取相应的行动。

这使您的以下代码:

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

完全不相关且不受欢迎。

ListBoxItems不是你关心的问题。您的代码不应使用或引用它们。这取决于 UI 来创建正确的ListBoxItems给定数据结构并Collections提供。

这是您从传统的 UI 编程转变为基于 MVVM 的 XAML 框架时必须具备的最重要的认识。

因此,在 WPF 中创建新 UI 时,您必须始终做的第一件事是创建适当的数据结构:

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

    public Action Action { get; set; }
}

这是将由 表示的数据ListBoxItems,其中一个DisplayName要显示在 UI 中,还有一个要执行的Action委托。

根据链接的 MSDN 文章,System.Action委托

封装一个没有参数且不返回值的方法。

因此,它非常适合我们当前的需求。我们需要一个不带参数且不返回任何内容的方法(实际上是委托)的引用,例如:

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

另请注意,C# 支持匿名方法Lambda 表达式的概念,以便以更短的语法实际编写这些简单的方法。

例如,上面SimpleMethod()可以简化为 Lambda 表达式,如:

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

这消除了声明附加标识符(方法名称)的需要,并简化并有助于保持代码清洁。

回到我们的示例,创建 WPF UI 时需要的第二件事是ViewModel,这个类实际上表示(并保存)将显示在屏幕上的数据:

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());
    }
}

请注意,此类与任何 UI 元素都没有交互(也没有引用)。在 MVVM(WPF 的首选和更简单的方法)中,应用程序逻辑和数据必须与 UI 完全分离。这使得两个部分的高度可定制性成为可能,而彼此之间没有过多的相互依赖。

另请注意,我正在定义 2 ObservableCollection<ActionItem>,这些将由 2 ListBoxes 在屏幕上表示,而FocusedAction1FocusedAction2表示 each 中突出显示的项目ListBox,最后string Result是用于存储结果的属性。

另请注意,为了支持双向数据绑定,ViewModel 类必须实现INotifyPropertyChanged接口,因此我们的 ViewModel 派生自一个PropertyChangedBase如下所示的类:

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));
            }));
    }
}

接下来,我们可以继续实际定义我们的 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>

请注意,我没有在 XAML 中命名任何元素。当您需要习惯 MVVM 心态时,这会很有帮助。无法从后面的代码中实际操作任何 UI 元素,这让您每次感到这样做的诱惑时都会重新考虑您的方法。

同样,我广泛使用DataBinding将 UI 连接到ViewModel,这就是消除了在代码中操作 UI 的需要的原因。

您可能已经注意到这种方法的另一个非常重要的方面:将样板代码减少到几乎为零。没有铸造的东西,没有ToString()什么,只有简单的属性和 DataBinding,这就是你在 WPF 中开发的方式。

最后,后面的代码和一些事件处理程序。我通常更喜欢使用命令而不是按钮的单击处理程序,但为了这个示例的简单性,我将坚持传统的方法:

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();
    }
}

请注意 Click 处理程序是如何简化为在 ViewModel 中执行逻辑的。这是一个关键概念,您绝不能将应用程序逻辑放在 Code Behind中。

所有这些都给出了以下结果:

在此处输入图像描述

注意点击RunButton的时候,Action右边所有的s都是按顺序执行的。我们的工作完成了。不switch,没有铸造,没有ListBoxItem.任何东西,没有复杂的视觉树操作。更具可维护性、可扩展性和美观的代码。这就是 WPF 和 MVVM 帮助产生的。

Commands编辑:根据 OP 的要求添加带有 的示例:

WPF 中的命令作为“用户操作”的抽象(不仅是按钮单击),例如,aKeyGesture可以与命令相关联:

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

此外,许多“可点击”的 UI 元素(MenuItemButtonCheckBoxToggleButton)已经有一个Command属性,您可以将其绑定到ICommandViewModel 中的某些实现。

这使得将 UI 元素连接到 ViewModel 中定义的某些行为变得非常容易。同样,这里的主要目标是将 UI 元素及其事件与实际的实现代码分开。

这是ICommand我能想到的最简单的可重用实现:

    //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;
        }
    }

因此,我们现在可以在现有的类型中声明此类型的一些属性ActionsViewModel

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

并在构造函数中实例化它们:

public ActionsViewModel()
{
   //Existing code here

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

请注意,构造函数中Command带有一个Action参数(同样是对方法的引用),该参数将在调用命令时执行。

因此,我们现在为这些命令替换 XAML 中引用的 Click 处理程序:

 <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}"/>

然后从后面的代码中删除不再需要的 Click 处理程序,并且由于我们还从那里删除了几乎所有代码,我们甚至不再需要对 ViewModel 的引用:

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

结果与以前完全相同,但我们现在将更多内容从 Code Behind 移到了 ViewModel 中。这只是一个简单的例子,但是还有更复杂的命令类型,例如Prism 的DelegateCommand<T>

同样重要的是要提到,如果 Command 的方法在引发事件后CanExecute()评估为,所有相关的 UI 元素(其属性绑定/设置为相关 Command 的 IE 元素),这些 UI 元素实际上会自动禁用。仔细想想,这真的很方便,因为在 ViewModel 中可能有许多 UI 元素绑定到相同的命令,而您不必单独管理这些 UI 元素。falseCanExecuteChangedCommand

于 2013-04-29T03:41:39.903 回答