如何将列表框的selectedItem传递给视图模型

时间:2011-06-07 17:05:17

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

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

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

是否有办法将所选项目从视图传递到viewmodel,并在按钮从模板化列表框项目中调用命令时阻止将值重置为null?

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

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

原帖如下:

在我的项目中,我有一个listbox usercontrol,其中包含一个自定义数据模板。

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

我遇到的问题是在选择列表框项目时,将触发selectionchanged事件,并使用所选项目中的正确ID更新属性SelectedItemID。当我单击扩展器时,将触发Command,但属性SelectedItemID设置为null。我已经使用Silverlight-Spy跟踪了这个事件,并且事件与单击扩展器时所期望的一致,列表框项目失去焦点,扩展器(切换)获得焦点并且有一个LeftMouseDownEvent但是我看不到任何发生的事情解释了为什么该属性被设置为null。我将选择更改事件中使用的相同代码添加到listboxt项目上的LostFocus事件,但仍然收到相同的结果。

我很感激帮助理解为什么当作为listbox控件的一部分的扩展器按钮被设置为null时,公共属性SelectedItemID被设置为null。当然,在学习如何防止将属性设置为null并保留绑定ID时,我真的很感激。

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

我的下一次尝试是将构造函数中的扩展器控件的datacontext设置为

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

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

提前致谢

2 个答案:

答案 0 :(得分:0)

使用此行,您可以使用其默认构造函数创建新的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类型的属性,但我认为这已经完成。

答案 1 :(得分:0)

虽然这是一种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));
        }
    }