【问题标题】:How to pass the selectedItem of a listbox to the View Model如何将列表框的 selectedItem 传递给视图模型
【发布时间】:2011-09-10 06:27:53
【问题描述】:

这是一个运行中的问题,我已对其进行了更新,希望能更清楚一点。 简而言之,我想要完成的是将一个属性从列表框选定项传递给视图模型,以便可以在新查询中使用该属性。在下面的代码中,列表框从父对象继承数据绑定。列表框包含用于呈现详细结果的数据模板(用户控件)。

我遇到的问题是,在用户控件中,我有一个扩展器,单击该扩展器会从 ViewModel 调用命令。从我可以看到 Listbox 对象正在失去它的数据上下文,因此为了在扩展器展开时调用命令,我必须显式设置扩展器的数据上下文。这样做似乎会实例化一个新的视图模型,它将我的绑定属性 (SelectedItemsID) 重置为 null。

当按钮从模板列表框项目中调用命令时,有没有办法将选定项目从视图传递到视图模型并防止值被重置为空?

我意识到 Prism 和 MVVMLite 都有解决此问题的方法,但我对这两个框架都不熟悉,所以我不知道将其中任何一个应用到我的项目中的复杂程度。

这可以在 Prism 或 MVVMLite 之外完成吗?

原帖如下:

在我的项目中,我有一个包含自定义数据模板的列表框用户控件。

<ListBox x:Name="ResultListBox"
             HorizontalAlignment="Stretch"
             Background="{x:Null}"
             BorderThickness="0"
             HorizontalContentAlignment="Stretch"
             ItemsSource="{Binding SearchResults[0].Results,
                                   Mode=TwoWay}"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             SelectionChanged="ResultListBox_SelectionChanged">
        <ListBox.ItemTemplate>

            <DataTemplate>
                <dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
                    <!--  CFS Template  -->
                    <dts:TypeTemplateSelector.CFSTemplate>
                        <DataTemplate>
                                <qr:srchCFS />
                        </DataTemplate>
                    </dts:TypeTemplateSelector.CFSTemplate>

                    <!--  Person Template  -->
                    <dts:TypeTemplateSelector.PersonTemplate>
                        <DataTemplate>
                                <qr:srchPerson /> 
                        </DataTemplate>
                    </dts:TypeTemplateSelector.PersonTemplate>

                   <!-- removed for brevity -->

            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

SelectionChanged 从后面的代码调用下面的方法

private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (((ListBox)sender).SelectedItem != null)
        _ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
        this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
    }

在 ViewModel 中我有以下属性

public string SelectedItemID
    {
        get
        {
            return this._SelectedItemID;
        }
        set
        {
            if (this._SelectedItemID == value) 
                return;
            this._SelectedItemID = value;
        }

    }

列表框模板包含一个带有扩展控件的自定义布局。扩展器控件用于显示与所选项目相关的更多详细信息。这些详细信息(集合)是通过对我的代理进行新调用来创建的。要使用扩展器控件执行此操作,我使用了 Expressions InvokeCommandAction

<toolkit:Expander Height="auto"
                          Margin="0,0,-2,0"
                          Foreground="#FFFFC21C"
                          Header="View Details"
                          IsExpanded="False"
                          DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
                          Style="{StaticResource DetailExpander}">

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Expanded">
                    <i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>

在 ViewModel 中,调用的委托命令 GetCFSResultCommandExecute 非常简单

private void GetCfsResultCommandExecute(object parameter)
    {
        long IdResult;
        if (long.TryParse(SelectedItemID, out IdResult))
        {
            this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}

我遇到的问题是在选择listbox项目时,选择中间的事件触发和属性selectedItemid从所选项目的正确ID更新。当我单击扩展器时,会触发 Command 但属性 SelectedItemID 设置为 null。我已经用 Silverlight-Spy 跟踪了这一点,事件与单击扩展器时您所期望的一致该属性被设置为空。我将选择更改事件中使用的相同代码添加到 listboxt 项上的 LostFocus 事件中,但仍然收到相同的结果。

当作为列表框控件一部分的展开器按钮设置为空时,我将不胜感激理解为什么将公共属性 SelectedItemID 设置为空。当然,在学习如何防止将属性设置为 null 并保留绑定 ID 方面,我非常感谢任何帮助。

更新 我试图从 Expander 中删除 datacontext 引用,因为这被认为是问题所在。从我所拥有的,因为这是一个数据模板项,它“步”出可视化树并失去对从父对象继承的控件的数据上下文的引用。如果我尝试在代码中为控件设置数据上下文,则所有与属性的绑定都会丢失。

我的下一个尝试是将构造函数中扩展器控件的数据上下文设置为

private SearchViewModel _ViewModel;
    public srchCFS()
    {
        InitializeComponent();
        this.cfsExpander.DataContext = this._ViewModel;
    }

这种方法似乎不起作用,因为 InvokeCommandAction 从未被触发。该命令似乎仅在扩展器上设置了数据上下文时触发。

提前致谢

【问题讨论】:

  • 你比它需要的更难很多。只需将 SelectedItem 绑定回 ViewModel 并从该项目中获取 Id。尝试将 SelectedItem 绑定到 Model 的属性将需要您做大量的提升,因为 ItemsControl 期望 SelectedItem 是其 ItemsSrouce 属性中的一个实例,而不是其中一个实例的 property
  • @Will,谢谢我会尝试这个,但它似乎没有工作 SelectedItem="{Binding SelectedItemID, Mode=TwoWay}" 我假设这可能是由于列表框项目是用户由一组属性组成的控件?
  • SearchResults[0].Results 是 X 类型的集合。您在 ViewModel 上公开了 X 类型的 DP,称之为SelectedX。然后将它绑定到SelectedItem="{Binding SelectedX}",这样当您选择列表中的一项时,该项目就会在SelectedX 中找到。然后,您可以通过 SelectedX.Id 获取该 ID。

标签: silverlight data-binding mvvm expression-blend inotifypropertychanged


【解决方案1】:

通过这一行,您可以使用其默认构造函数创建一个新的 SearchViewModelDataSource。

DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"

我想这就是为什么你会发现 null 因为这是引用类型的默认值。 您可以通过将 DataContext 设置为与主控件相同的实例来解决此问题(您可以在所有组件初始化后通过代码来完成)。

希望对您有所帮助!


编辑

我认为从代码设置 datacontext 后绑定不会丢失。每次我需要在两个或多个模型之间共享某些内容时,我都会这样做。
关于您编写​​的代码:

private SearchViewModel _ViewModel;
public srchCFS()
{
    InitializeComponent();
    this.cfsExpander.DataContext = this._ViewModel;
}

您可以尝试使用 FindName 方法,而不是使用 this.cfsExpander。也许这会返回正确的实例。

object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
    Expander exp = item as Expander;
    exp.DataContext = this._ViewModel;
 }

试试它是否适合你。
当然,this._ViewModel 必须公开一个名为 GetCfsResultCommand 的 ICommand 类型的属性,但我认为这已经完成了。

【讨论】:

  • LoSciamano,您能否提供一个示例,说明如何在初始化后在代码中进行设置> 我已经尝试过,但我的方法仍然失败。谢谢
  • 由于这是一个自定义的列表框模板,它在可视化树之外进行。因此它不会从父对象继承数据上下文。为了让命令触发,似乎我必须为 xaml 中的扩展器设置 datacontenxt。我已更新我的问题以反映所做的更改。
  • 感谢 LoSciamano,虽然在删除 xaml 中的引用后面的代码中设置了 dataContext,但会阻止调用命令 GetCfsResultCommand。将数据上下文放置在 xaml 中允许调用命令,但该属性随后设置为 null。还有其他建议吗?
  • 在这种情况下,我会尝试使用 CallMethodAction 而不是 InvokeCommandAction。 DataContext 初始化必须在包含 ListBox 的 UserControll (o Page) 的构造函数中完成。从您发布的代码看来,您在 srchCFS 的构造函数中对其进行了初始化,如果我错了,请纠正我,是 ListBox 中用作数据模板的 Controll。
  • 由于这是一个委托命令,CallMethodAction 不会从视图模型调用命令。我确实返回并将 FindName 方法放在父列表框用户控件中,就像您怀疑将 srchCFS 用户控件加载为数据模板一样。不幸的是,如果没有在 xaml 中设置数据上下文,还没有任何工作可以防止值被重置或从模型调用命令。
【解决方案2】:

虽然这是一种 hacky 方法,但我找到了一种中间解决方案,可以将列表框项值传递给视图模型。我最终使用了选择更改事件并将值直接传递给我的视图模型中的公共属性。不是最好的方法,但它在短期内解决了问题

private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (((ListBox)sender).SelectedItem != null)
            _ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
            MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
        this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
    }

为此,我还必须在视图中设置一个属性更改处理程序,以将更改推送到 VM。您可以忽略 MySelectedValue 行,因为它是我用于测试的辅助代码。

对于那些感兴趣的通用属性更改处理程序

        public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

【讨论】:

    猜你喜欢
    • 2011-08-10
    • 2017-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-05
    • 2020-04-16
    • 1970-01-01
    相关资源
    最近更新 更多