一直试图绕过代表们,但是当它沉入其中时似乎总是碰壁。
在阅读有关代表的文章后,我试图让自己的例子帮助我理解 - > 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;
}
}
答案 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 Methods和Lambda 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中执行逻辑。这是一个关键概念,您绝不能将应用程序逻辑放在代码隐藏中。
所有这些都给出了以下结果:
请注意,单击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>
,MenuItem
,Button
,CheckBox
)已经具有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元素。