MVVM - 在对象列表中,每个对象都包含一个命令,如何获取要触发的适当命令?
也许我的处理方式是错误的,但在我的视图模型中,我有一个对象列表(CGCAppSwitchboardItem),每个对象都包含一个 DelegateCommand 属性。我的意图是列表中的每个项目 (CGCAppSwitchboardItem) 都代表解决方案中的一个模块,通过调用该命令将加载一个模块。或者,更好地说,我使用此列表来构建按钮或菜单项列表,这些按钮或菜单项在选择时将打开请求的视图。
我遇到的问题是,无论按下按钮,最后一个 CGCAppSwitchboardItem 的命令都会被触发。我通过以不同的顺序添加它们来测试这一点,无论最后添加哪个,这就是被触发的命令。我如何获得适当的开火命令?
编辑:我已经缩小了问题范围,但不知道该怎么办。问题出现在我将其分配给 RunMethod 时创建的通用函数中。如果您查看下面的代码,我会在创建 CGCAppSwitchboardItem 类时分配 RunMethod。如果您查看我的通用函数,我会传入 itm.ModuleName 属性。显然,新创建的委托(我分配给 RunMethod 的委托)保留对 itm.ModuleName 的引用,而不是我想要的字符串值。由于这是在循环中,因此 itm 变量保持设置为列表中的最后一个模块。因此,当调用 RunMethod() 时,它引用 itm.ModuleName 而不是“MyModuleName”。有什么想法吗?
public class CGCAppSwitchboardItem : ISwitchboardItem
{
public Action RunMethod { get; set; } //RunMethod gets assigned by external ViewModel
public ICommand ExecuteCommand { get; set; }
public CGCAppSwitchboardItem()
{
ExecuteCommand = new DelegateCommand<object>(
o =>
{
if (RunMethod != null)
{
RunMethod();
}
},
o =>
{
return (RunMethod != null);
});
}
}
public class CGCApplicationShellViewModel : ICGCApplicationShellViewModel, INotifyPropertyChanged, ISwitchboardListContainer
{
//...为简洁起见删除了代码 私有无效 PopulateSwitchboardItems() { if (_moduleCatalog != null) { _switchBoardItems.Clear();
foreach (var itm in _moduleCatalog.Modules)
{
_switchBoardItems
.Add(
new CGCAppSwitchboardItem()
{
Name = itm.ModuleName,
RunMethod = () =>
{
System.Windows.Forms.MessageBox.Show(itm.ModuleName); _moduleManager.LoadModule(itm.ModuleName); //_moduleManager.LoadModule("ATimesheetModule"); } } ); } } }
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:WTSSwitchBrdItm="clr-namespace:WTS.CGCApplicationInterface.Switchboard"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="150">
<UserControl.Resources>
<DataTemplate x:Key="ShowModule">
<Button Name="btnOpenView" Height="Auto" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto"
Command="{Binding ExecuteCommand}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="imgAvatar" Source="{Binding Path=Avatar}" Grid.Column="0" Grid.Row="0" Width="50" Height="50"/>
<TextBlock x:Name="txtName" Text="{Binding Path=Name}" Grid.Column="1" Grid.Row="0" />
<TextBlock x:Name="txtDescription" Margin="0, 5, 0, 5" Grid.Column="1" Grid.Row="3" >
<TextBlock.Text>
<MultiBinding StringFormat=" ({0})">
<Binding Path="Description"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=SwitchboardItems}" ItemTemplate="{StaticResource ShowModule}"></ListBox>
</Grid> </UserControl>
Maybe I'm going about this the wrong way but in my view model I have a list of objects (CGCAppSwitchboardItem) that each contain a DelegateCommand property. My intention here is that each item (CGCAppSwitchboardItem) in my list would represent a module inside my solution whereby a call to the command will load a module. Or, better said, I am using this list to build a list of buttons or menu items that will open the requested view when selected.
The problem that I have is that the last CGCAppSwitchboardItem's command is the one that gets fired regardless of the button pushed. I have tested this by adding them in different order, and whichever is added last, that's the command that gets fired. How can I get the appropriate command to fire?
Edit: I've narrowed the problem down and don't know what to do about it. The problem is occurring in the generic function that I'm creating when assigning it to the RunMethod. If you look at the code below, I'm assigning the RunMethod when creating the CGCAppSwitchboardItem class. If you look at my generic function, I'm passing in the itm.ModuleName property. Apparently, the newly created delegate, the one I'm assigning to the RunMethod, is keeping a reference to itm.ModuleName instead of the string value to which I want. Since this is in a loop, the itm variable remains set to the last module in the list. Therefore, when the RunMethod() is called, it references itm.ModuleName instead of "MyModuleName". Any ideas?
public class CGCAppSwitchboardItem : ISwitchboardItem
{
public Action RunMethod { get; set; } //RunMethod gets assigned by external ViewModel
public ICommand ExecuteCommand { get; set; }
public CGCAppSwitchboardItem()
{
ExecuteCommand = new DelegateCommand<object>(
o =>
{
if (RunMethod != null)
{
RunMethod();
}
},
o =>
{
return (RunMethod != null);
});
}
}
public class CGCApplicationShellViewModel : ICGCApplicationShellViewModel, INotifyPropertyChanged, ISwitchboardListContainer
{
//...REMOVED CODE FOR BREVITY
private void PopulateSwitchboardItems()
{
if (_moduleCatalog != null)
{
_switchBoardItems.Clear();
foreach (var itm in _moduleCatalog.Modules)
{
_switchBoardItems
.Add(
new CGCAppSwitchboardItem()
{
Name = itm.ModuleName,
RunMethod = () =>
{
System.Windows.Forms.MessageBox.Show(itm.ModuleName);
_moduleManager.LoadModule(itm.ModuleName);
//_moduleManager.LoadModule("ATimesheetModule");
}
}
);
}
}
}
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:WTSSwitchBrdItm="clr-namespace:WTS.CGCApplicationInterface.Switchboard"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="150">
<UserControl.Resources>
<DataTemplate x:Key="ShowModule">
<Button Name="btnOpenView" Height="Auto" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto"
Command="{Binding ExecuteCommand}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="imgAvatar" Source="{Binding Path=Avatar}" Grid.Column="0" Grid.Row="0" Width="50" Height="50"/>
<TextBlock x:Name="txtName" Text="{Binding Path=Name}" Grid.Column="1" Grid.Row="0" />
<TextBlock x:Name="txtDescription" Margin="0, 5, 0, 5" Grid.Column="1" Grid.Row="3" >
<TextBlock.Text>
<MultiBinding StringFormat=" ({0})">
<Binding Path="Description"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=SwitchboardItems}" ItemTemplate="{StaticResource ShowModule}"></ListBox>
</Grid> </UserControl>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以包含 CommandParameter 来标识记录:
You can include the CommandParameter to identify the record: