简体   繁体   中英

How to update the View from a ViewModel

I have a viewmodel ProductsViewModel , and one of the methods adds a new Product to the ProductList that it stores. I currently have a ListBox bound to the ProductList . I add a new product by having a button bound to a simple Command which calls the relevant method on the viewmodel.

How do I modify the view to select the new Product that's been added to the ListBox and scroll down to the new item when the view model can't 'talk' to the view?

Edit

Note, I do not want the last item to be automatically selected every time a new item is added to the listbox, because that will select the last item when I import items to the listbox which I want to avoid.

在ViewModel'SelectedProduct'中创建一个属性(显然,它需要提高属性。在将新产品添加到ProductList之后,还要使用此新产品更新SelectedProduct。在视图中,将ListBox的SelectedItem绑定到CurrentProduct。

In general the best way to achieve this is with a behaviour. The implementation will likely depend on your specific requirements but what I'll provide here is a generic example that shows how to make the view model trigger the ListBox to scroll to a specific item of your choosing.

First of all you need a way of communicating this from the view model to the view, you can't bind directly to events in XAML but you can encapsulate the event in a wrapper and bind to that instead:

public class ListBoxScrollHandler
{
    public event Action<object> ScrollEvent;

    public void ScrollTo(object item)
    {
        if (this.ScrollEvent != null)
            this.ScrollEvent(item);
    }
}

That class contains an event which our behaviour can bind to and a ScrollTo method that our view model can invoke. For the view let's just create a simple listbox that we'll fill with numbers (actually strings) and a button that will force us to scroll to the element with the content "500":

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition />
    </Grid.RowDefinitions>

    <Button Content="Scroll to 500" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ScrollCommand}" CommandParameter="500" />
    <ListBox Grid.Row="1" ItemsSource="{Binding MyItems}" SelectedItem="{Binding CurrentItem}" ScrollViewer.VerticalScrollBarVisibility="Visible">
        <i:Interaction.Behaviors>
            <behaviors:ListBoxScrollBehavior ScrollHandler="{Binding ScrollHandler}" />
        </i:Interaction.Behaviors>
    </ListBox>
</Grid>

As you can see I've implemented this with a Blend behaviour, you can of course do it with a regular attached behaviour if you want but I'm keeping things simple here:

public class ListBoxScrollBehavior : Behavior<ListBox>
{
    public ListBoxScrollHandler ScrollHandler
    {
        get { return (ListBoxScrollHandler)GetValue(ScrollHandlerProperty); }
        set { SetValue(ScrollHandlerProperty, value); }
    }

    public static readonly DependencyProperty ScrollHandlerProperty =
        DependencyProperty.Register("ScrollHandler", typeof(ListBoxScrollHandler),
        typeof(ListBoxScrollBehavior), new PropertyMetadata(null, OnScrollHandlerChanged));

    protected override void OnAttached()
    {
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    private static void OnScrollHandlerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as ListBoxScrollBehavior;
        if (behavior == null)
            return;

        var oldHandler = e.OldValue as ListBoxScrollHandler;
        if (oldHandler != null)
            oldHandler.ScrollEvent -= behavior.ScrollTo;

        var newHandler = e.NewValue as ListBoxScrollHandler;
        if (newHandler != null)
            newHandler.ScrollEvent += behavior.ScrollTo;
    }

    public void ScrollTo(object item)
    {
        this.AssociatedObject.ScrollIntoView(item);
    }

}

So our behaviour contains a "ScrollHandler" dependency property that we can bind to our view model and responds by calling the listbox's ScrollIntoView method. After that it's simply a matter of creating a view model that provides this property along with code to initialize the list items and a command handler that responds to the button press and invokes it's scroll handler's ScrollTo method:

public class MainViewModel : ViewModelBase
{
    private ObservableCollection<string> _MyItems = new ObservableCollection<string>();
    public ObservableCollection<string> MyItems
    {
        get { return this._MyItems; }
        set { this._MyItems = value; RaisePropertyChanged(); }
    }

    private string _SelectedItem;
    public string SelectedItem
    {
        get { return this._SelectedItem; }
        set { this._SelectedItem = value; RaisePropertyChanged(); }
    }

    public ICommand ScrollCommand { get { return new RelayCommand<string>(OnScroll); } }
    private void OnScroll(string item)
    {
        this.ScrollHandler.ScrollTo(item);
    }

    private ListBoxScrollHandler _ScrollHandler = new ListBoxScrollHandler();
    public ListBoxScrollHandler ScrollHandler
    {
        get { return this._ScrollHandler;}
        set { this._ScrollHandler = value; RaisePropertyChanged(); }
    }

    public MainViewModel()
    {
        for (int i = 0; i < 1000; i++)
            this.MyItems.Add(i.ToString());
    }
}

Run the code, click the button and the listbox will scroll down to the element containing the "500" content. Obviously if you only need a sub-set of this behaviour (eg scroll to the currently selected item) then you can modify this behaviour accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM