WPF列表框选择更改MVVM

27
在我的WPF应用程序中有一个列表框。我知道如何使用selectionchanged事件。但是,我正在尝试遵循MVVM设计模式。但是我不确定该怎么做。
我已经为按钮完成了这个过程,但是不确定是否可以做同样的事情?
<Button Grid.Column="0" Name="buttImport" 
    Content="Import File" 
    Command="{Binding CommandButtImport}" 
    Style="{StaticResource ButtonTemplate}"/>

 

public class ViewModel : INotifyPropertyChanged 
{       
    // for the button that imports the orders file
    public ICommand CommandButtImport { get; set; }

    public ViewModel() 
    {
        CommandButtImport = new MyCommands(
            ExecuteCommandButtImport,
            CanExecuteCommandButtImport);
    }

    private bool CanExecuteCommandButtImport(object parameter)
    {
        return true;
    }

    // import button
    private void ExecuteCommandButtImport(object parameter)
    {
      // some code
    }
}

编辑 请忽略上面的代码

我已经更改了代码,下面是我目前拥有的。我遇到了一个奇怪的问题。XAML-Main Code 包含了我的 datagrid 的代码。App - XAML 下面的代码块包含了大部分应用程序的样式,但只是一个片段。

此外,

在 XAML - Main Code 中,在我的 datagrid 下方添加了一行代码进行测试。

<ListBox ItemsSource="{Binding SelectedItem.DuplicateSecurities, ElementName=dataGridOrders}" 
 SelectedItem="{Binding SelectedItem.Security, ElementName=dataGridOrders}"/>

我的数据表格加载正常。当我单击一行时,该行会展开以显示证券列表。问题在于,在此列表框中,当我单击项目时,什么也不会发生。然而,我添加到我的数据表格下方进行测试的列表框确实有效。例如,当我单击其中一个项目时,我的行会更新,同时我的行详情中的列表框也被选中。非常奇怪的是,为什么我的行详情中的列表框不起作用,但我的数据表格下面的那个可以。有任何想法吗?
XAML-主要代码
<StackPanel>
        <!-- The data grid to display orders-->
        <DataGrid DataContext="{Binding OrderBlock}" 
                  x:Name="dataGridOrders" 
                  ItemsSource="{Binding Orders}"
                  Style="{StaticResource DataGridTemplate}"
                  ColumnHeaderStyle="{StaticResource DG_ColumnHeader}"                      
                  RowHeaderStyle="{StaticResource DG_RowHeader}"
                  RowStyle="{StaticResource DG_Row}"
                  CellStyle="{StaticResource DG_Cell}"                      
                  RowDetailsTemplate="{StaticResource DG_RowDetail}"                      
                  AutoGenerateColumns="False"
                  HorizontalAlignment="Stretch" 
                  VerticalAlignment="Stretch"
                  Background="Silver"
                  RowHeaderWidth="30"                      
                  Margin="25,5,20,15">                                                     

            <DataGrid.Columns>                    
                <DataGridComboBoxColumn Header="Action">
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="ComboBox">
                            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.StatusList}"/>
                            <Setter Property="IsReadOnly" Value="True"/>
                            <Setter Property="SelectedValue" Value="{Binding StatusGood}"/>
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="ComboBox">
                            <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.StatusList}"/>
                            <Setter Property="IsReadOnly" Value="True"/>
                            <Setter Property="SelectedValue" Value="{Binding StatusGood}"/>
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>                    
                <DataGridTextColumn Header="Fund" Binding="{Binding Account}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Security ID" Binding="{Binding Security.ID}" IsReadOnly="True"/>
                <DataGridTextColumn Header="ThinkFolio Security ID" Binding="{Binding ThinkFolioSecurityID}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Security Name" Binding="{Binding Security.Name}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Buy/Sell" Binding="{Binding TransType}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Quantity" Binding="{Binding OrderQunatity, StringFormat=\{0:N0\}}" IsReadOnly="False"/>
                <DataGridTextColumn Header="Currency" Binding="{Binding BuyCurrency}" IsReadOnly="False"/>
                <DataGridTextColumn Header="Manager" Binding="{Binding FundManager}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Order Reason" Binding="{Binding OrderReason}" IsReadOnly="True"/>
                <DataGridTextColumn Header="Reject Reason" Binding="{Binding RejectReason}" IsReadOnly="True" Width="*"/>                    
            </DataGrid.Columns>
        </DataGrid>
        <ListBox ItemsSource="{Binding SelectedItem.DuplicateSecurities, ElementName=dataGridOrders}" SelectedItem="{Binding SelectedItem.Security, ElementName=dataGridOrders}"/>
        </StackPanel>

应用程序XAML

  <!-- Row Detail Template for Data Grid -->
    <DataTemplate x:Key="DG_RowDetail">
        <Grid x:Name="RowDetailGrid"                  
              Margin="5"
              HorizontalAlignment="Left">
            <Border HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Width="500"
                    Height="80"
                    CornerRadius="5">
                <Border.Background>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                        <GradientStop Offset="0" Color="Transparent"/>
                        <GradientStop Offset="1" Color="Transparent"/>
                    </LinearGradientBrush>
                </Border.Background>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="2.5*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/> 
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0"
                               Grid.ColumnSpan="3"
                               Margin="5,0,0,5"
                               HorizontalAlignment="Left"
                               FontSize="12"
                               FontWeight="Bold"
                               Foreground="Black"
                               Text="Select Security Identifier">
                    </TextBlock>
                    <ListBox Grid.Row="1" Grid.ColumnSpan="3" Name="lbIdentifier" ItemsSource="{Binding DuplicateSecurities}" SelectedItem="{Binding Security}"                                 
                             SelectionMode="Single" HorizontalContentAlignment="Stretch">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid Margin="0,2">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <TextBlock Grid.Column="0" Text="{Binding Path=ID}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
                                    <TextBlock Grid.Column="1" Text="{Binding Path=Name}" FontSize="10" HorizontalAlignment="Left" Margin="5,0,0,0"/>
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </Border>                                        
        </Grid>
    </DataTemplate>

视图模型

public class ViewModel : INotifyPropertyChanged 
{       
    public ICommand CommandButtImport { get; set; }                     // for the button that imports the orders file
    public ICommand CommandButtSend { get; set; }                       // the button where the user sends the orders in our data grid to thinkFolio
    public ICommand CommandButtExit { get; set; }                       // exit application

    private QoEMain _QoEManager;                                        // manages the Model
    public QoEMain QoEManager { get { return this._QoEManager; } set { _QoEManager = value; } }

    private OrderBlocks _orderBlock;                                    // order block - contains all the order information
    public OrderBlocks OrderBlock
    {
        get
        {
            return this._orderBlock;
        }
        set
        {
            this._orderBlock = value;
            OnPropertyChanged("OrderBlock");
        }
    }
 }

OrderBlocks类,其中包含其他类

 public class OrderBlocks : INotifyPropertyChanged
{
 private List<Order> _orders;
    [XmlElement("tF_Transactions")]
    public List<Order> Orders { get { return _orders; } set { _orders = value; OnPropertyChanged("Orders"); } }
}

订单类

    public class Order : INotifyPropertyChanged
    {
        Security security;
        public Security Security
        {
            get { return security; }
            set { security = value; OnPropertyChanged("Security"); }
        }

        List<Security> duplicateSecurities;
        public List<Security> DuplicateSecurities
        {
            get { return duplicateSecurities; }
            set { duplicateSecurities = value; OnPropertyChanged("DuplicateSecurities"); }
        }

安全类

 public class Security : INotifyPropertyChanged
 {
    private string _id;
    public string ID
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
            OnPropertyChanged("ID");
        }
    }

    private string _name;
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            OnPropertyChanged("Name");
        }
    }

    public Security() { }

    public Security(string newID, string newName)
    {
        ID = newID;
        Name = newName;
    }

编辑 - 我的代码现在可以正常工作,以下是对我有效的代码片段

<DataGrid Grid.Row="1" Grid.Column="0" 
     ItemsSource="{Binding SelectedItem.DuplicateSecurities, ElementName=dataGridOrders}" 
     SelectedItem="{Binding SelectedItem.Security, ElementName=dataGridOrders}"> 

6
而问题是.... - dowhilefor
你能否发布一下你尝试过的内容以及这些尝试的结果? - alan
我上面发布的示例适用于按钮,但我想知道如何在列表框选择更改时执行此操作。 - mHelpMe
1
使用MVVM,将ListBox的SelectedItem或SelectedIndex属性绑定到VM,并在属性的setter中执行所需操作,因为它会在ListBox中的选择更改时触发。这是否是您的问题所在? - Viv
在我的虚拟机中,我已经添加了以下代码:private string _selectedID { get; set; } public string SelectedID { get { return _selectedID; } set { if (value == _selectedID) return; else _selectedID = value; OnPropertyChanged("SelectedID"); } } - mHelpMe
显示剩余2条评论
3个回答

55

将ListBox的SelectionChanged事件与ViewModel中的命令绑定的示例

<ListBox x:Name="myListBox" ItemsSource="{Binding SomeCollection}">
   <ie:Interaction.Triggers>
      <ie:EventTrigger EventName="SelectionChanged">
        <ie:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"  CommandParameter="{Binding ElementName=myListBox, Path=SelectedItem}"/>
    </ie:EventTrigger>
  </ie:Interaction.Triggers>
</ListBox >

在你的ViewModel中:

public class myViewModel
{

    public myViewModel()
    {
        SelectedItemChangedCommand = new DelegateCommand<object>((selectedItem) => 
        {
             // Logic goes here
        });
    }

    public List<SomeData> SomeCollection { get; set; }

    public DelegateCommand<object> SelectedItemChangedCommand { get; set; }
}
这个例子使用的是Prism MVVM框架,但你同样可以将相同的思想应用到你正在使用的任何其他MVVM框架中。
希望这能有所帮助。

如果有人在使用本答案提供的解决方案时遇到错误,请参考我下面的答案:http://stackoverflow.com/a/26949499/2284240 - Vishal
15
你想在顶部使用以下命名空间,以便使用 "ie:":xmlns:ie="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" - MistaGoustan
1
谢谢,这帮助我入门了。只是提一下,您需要将System.Windows.Interactivity添加到项目的引用中。对于那些通常使用RelayCommand的人,只需像普通命令一样连接它,您就可以在参数中获取所选对象。 - Paul Palmpje
谢谢,这是一个非常好的解决方案... 我补充一下,如果你使用"caliburn.micro",你应该使用"RelayCommand"而不是"DelegateCommand",它会正常工作。 - ajd.nas
这个解决方案是无意义的。处理事件不违反MVVM。MVVM并不意味着强制使用ICommand来完成所有操作。虽然这是一篇旧文章,但我希望你现在更加了解它。您始终可以绑定到SelectedItemSelectedIndex以触发视图模型中的操作。 - BionicCode
1
如果列表只有一个项目,则无法运行。 - Minwoo Yu

25

在MVVM中使用SelectionChanged非常简单:

一种方法是绑定到ListBoxSelectedIndex属性,在VM中的属性设置器中根据需要进行操作,因为该属性在更改时会触发。

例如:这里

在这个例子中,每当选定索引改变时,该项的值就会增加一。

关键部分是:

public int SelectedIndex {
  get {
    return _selectedIndex;
  }

  set {
    if (_selectedIndex == value) {
      return;
    }

    // At this point _selectedIndex is the old selected item's index

    _selectedIndex = value;

    // At this point _selectedIndex is the new selected item's index

    RaisePropertyChanged(() => SelectedIndex);
  }
}

xaml只是这样的:

<ListBox ItemsSource="{Binding Items}" SelectedIndex="{Binding SelectedIndex}" />

Items是我们绑定到的项目集合。


7
@Omribitan Items[SelectedIndex] - Viv
1
@Omribitan 抱歉,在这里我不同意你的看法。如果您不想在setter中执行此操作,请将其调度回相同的线程或安装INPC监视器并相应地进行操作。这将允许直接从ViewModel更改选择,而不仅仅是UI,这不是您想要的。 错误什么了?从VM更改“SelectedIndex”是完全有效的设计。您可能有一些情况需要将索引设置为-1以删除选择和其他数百个用例。 - Viv
3
VM通常与View之间有一个一对一的关系。只需让VM执行其既定任务“管理相应的视图”。这是WPF。您可以想出10种方法来解决此问题。我唯一不同意的是“将SelectedIndex绑定到VM是错误的”这部分。选择您感到舒适的任何方法即可 :P - Viv
为了保险起见,最好还是有一份文档。我的工作非常谨慎! - mHelpMe
谢谢。当我在列表框中选择一个项目时,属性SelectedIndex的setter仍然没有被调用。itemsSource绑定到DuplicateHolder上。这是否有关系,因为selectedItem绑定到SelectedIndex,而DuplicateHolder对此一无所知? - mHelpMe
显示剩余10条评论

8

System.Windows.Interactivity.WPF 包已被弃用。

请安装 Microsoft.Xaml.Behaviors.Wpf 包。

<ListBox ItemsSource="{Binding ListItems}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedItem}">
     <Behaviors:Interaction.Triggers>
          <Behaviors:EventTrigger EventName="SelectionChanged">
               <Behaviors:InvokeCommandAction Command="{Binding ItemChangedCommand}" />
          </Behaviors:EventTrigger>
     </Behaviors:Interaction.Triggers>
</ListBox>

1
给你的列表框命名,并在InvokeCommandAction行中添加“CommandParameter =” {Binding ElementName = myListBox,Path = SelectedItem}“,以获取所选项目的引用。 - TheHof

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接