代表所需的协助示例

时间:2013-04-29 02:22:56

标签: c# wpf delegates

一直试图绕过代表们,但是当它沉入其中时似乎总是碰壁。

在阅读有关代表的文章后,我试图让自己的例子帮助我理解 - > 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;
    }
}

2 个答案:

答案 0 :(得分: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。

答案 1 :(得分:2)

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

在WPF编程时,您必须了解的第一件事是UI is not Data并采取相应的行动。

这使您的代码如下:

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

完全不相关且不受欢迎。

ListBoxItems不是您关注的问题。您的代码不应使用也不应引用它们。如果提供了正确的数据结构并且提供了ListBoxItems,则由UI创建正确的Collections

这是coming from traditional UI programming into MVVM-able XAML based frameworks时必须具备的最重要的实现。

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

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

    public Action Action { get; set; }
}

这是将由ListBoxItems表示的数据,其中DisplayName将显示在用户界面中,并且Action delegate将执行。

根据链接的MSDN文章System.Action代理

  

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

因此,它非常适合我们当前的需求。我们需要对方法的引用(代理实际上是什么),它不带任何参数,也不返回任何内容,如:

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

另请注意,C#支持Anonymous MethodsLambda Expressions的概念,以更短的语法实际编写这些简单方法。

例如,上述SimpleMethod()可以简化为Lambda Expression,如:

() => 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 ListBox es屏幕上显示的内容,而FocusedAction1和{{1}表示每个FocusedAction2中突出显示的项目,最后是用于存储结果的ListBox属性。

另请注意,为了支持Two Way DataBinding,ViewModel类必须实现INotifyPropertyChanged接口,因此我们的ViewModel派生自string Result类,如下所示:

PropertyChangedBase

接下来,我们可以继续实际定义我们的UI:

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

请注意,我没有在XAML中命名任何元素。当你需要习惯MVVM心态时,这有助于很多。无法从代码背后实际操作任何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> 将用户界面连接到DataBinding,这就是为什么消除了在代码中操作UI的需要。

您可能已经注意到此方法的另一个非常重要的方面:将样板减少到几乎为零。没有任何投射内容,没有ViewModel没有任何内容,只是简单,简单的属性和DataBinding ,这就是你在WPF中的开发方式。

最后,Code Behind和一些事件处理程序。我通常更喜欢使用Commands而不是按钮的Click处理程序,但为了简化此示例,我将坚持使用传统方法:

ToString()

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

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

enter image description here

请注意,单击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(); } } 按钮时,右侧的所有Run都会按顺序执行。我们的工作已经完成。没有Action,没有强制转换,没有switch任何东西,没有复杂的可视化树操作。更易于维护,可扩展且美观的代码。这就是WPF和MVVM有助于产生的东西。

修改:根据OP的要求,使用ListBoxItem.添加示例:

WPF中的命令用作&#34;用户操作&#34;的抽象。 (不仅是按钮点击),例如,KeyGesture可以与命令关联:

Commands

此外,许多&#34;可点击&#34; UI元素(<TextBox> <TextBox.InputBindings> <KeyBinding Key="Enter" Command="{Binding SomeCommand}"/> </TextBox.InputBindings> </TextBox> MenuItemButtonCheckBox)已经具有ToggleButton属性,您可以将其绑定到某些Command实现中视图模型。

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

以下是我能够提出的最简单的可重用ICommand实现:

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参数(同样,对方法的引用),这将在调用命令时执行。< / p>

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

Action

然后从Code Behind中删除不再需要的Click处理程序,因为我们也从那里删除了几乎所有代码,我们不再需要再引用ViewModel了:

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

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

在提出public partial class ActionsListBoxSample : Window { public ActionsListBoxSample() { InitializeComponent(); DataContext = new ActionsViewModel(); } } 事件后,如果Command的CanExecute()方法评估为false,则所有相关的UI元素(IE元素为其中CanExecuteChanged属性绑定/设置为相关命令,这些UI元素实际上是自动禁用的。当您考虑它时,这非常方便,因为可能有许多UI元素绑定到ViewModel中的相同命令,并且您不必单独管理这些UI元素。