开发者

WPF datagrid live-update with button-column

I have a datagrid which contains bid and ask - prices for currency-symbols. The data is updated every seconds. I update the data b开发者_StackOverflow社区y creating a new collection of my viewmodel-entities and bind this collection to the datagrid every second.

The problem is: Because my datagrid contains a template-column with a button "buy", this button is also recreated every second! This means, that when the user hovers the button, the hover-styles blinks, as the button is recreated every second. Additionally sometimes the click-event is not fired correctly, if the button is recreated while the user has his mouse-left-button pressed.

Any suggestions, how to solve real-time-update a datagrid with button-columns?


If I understand correctly, you have a collection of items and you have a couple fields (bid/ask in particular), all of which will be updated every second. It sounds like what may be happening is that in the process of changing the ItemsSource of your data grid, you're losing some important state that is causing problems for the event handlers on your buttons.

Even if you update all of the items, the important distinction to make is that you should update the items, and not completely clear out the collection that is currently bound to your datagrid. Changing the ItemsSource to a new one will cause the data grid to have to do a lot more work than if you simply update the contents of the existing collection. If you're using an ObservableCollection, this may mean making your viewmodel items mutable so that you can simply update bid/ask. If your viewmodel items are mutable and implement INotifyPropertyChanged, the bid/ask updates will be reflected in the datagrid or in any other bindings to those properties of the objects. The neat thing about doing it this way is that the same objects are staying bound to the same containers in the ItemsControl, so during each update, absolutely nothing is happening to your buttons. Now, if your viewmodel objects that contain bid/ask are immutable, you should still be able to pull this off. Every second, you simply iterate through your collection of items and use SetItem to replace each existing item with a new one. The important thing to remember in this latter case is that every second, the datagrid is still getting notified that there has been a change in the ObservableCollection, and because of this, the bindings on each row are going to cause the DataContext of the row/cells/button to update.

Here's a quick example of how I might go about this problem. I'm going to assume use of the datagrid in .NET 4.0 (if you're using toolkit though with 3.5, this should be the same). I'm going to take the first approach, where my CurrencyPair object is mutable.

First, some simple viewmodel code with a self contained timer to update a few currency pair bid/asks each second:

public class CurrencyPairsViewModel
{
    private readonly Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;
    private readonly ObservableCollection<string> _orders = new ObservableCollection<string>();
    private readonly ObservableCollection<CurrencyPair> _pairs = new ObservableCollection<CurrencyPair>();
    private readonly Random _rand = new Random();
    private readonly System.Timers.Timer _timer = new System.Timers.Timer(1000);
    private readonly Action _update;

    public CurrencyPairsViewModel()
    {
        this._timer.Elapsed += OnIntervalElapsed;
        this._update = new Action(this.Update);
        this._pairs.Add(new CurrencyPair("USD/GBP"));
        this._pairs.Add(new CurrencyPair("AUD/USD"));
        this._pairs.Add(new CurrencyPair("WOW/CAD"));
        this._timer.Start();
    }

    public ObservableCollection<string> Orders { get { return this._orders; } }

    public ObservableCollection<CurrencyPair> Pairs { get { return this._pairs; } }

    public void Buy(CurrencyPair pair)
    {
        this._orders.Add(string.Format("Buy {0} at {1}", pair.Name, pair.Ask));
    }

    private void OnIntervalElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        this._dispatcher.Invoke(this._update);
    }

    private void Update()
    {
        foreach (var pair in this._pairs)
        {
            pair.Bid = this._rand.NextDouble();
            pair.Ask = pair.Bid + 0.01;
        }
        this._timer.Start();
    }
}

public class CurrencyPair : INotifyPropertyChanged
{
    private readonly string _name;

    private double _ask;
    private double _bid;

    public CurrencyPair(string name)
    {
        this._name = name;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public double Ask
    {
        get { return this._ask; }
        set
        {
            this._ask = value;
            this.OnPropertyChanged("Ask");
        }
    }

    public double Bid
    {
        get { return this._bid; }
        set
        {
            this._bid = value;
            this.OnPropertyChanged("Bid");
        }
    }

    public string Name { get { return this._name; } }

    protected void OnPropertyChanged(string name)
    {
        if (null != this.PropertyChanged)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}

Second, the view, which in this example is just my MainWindow.

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
  <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="4"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row="0"
              ItemsSource="{Binding Pairs}"
              AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*"/>
            <DataGridTextColumn Header="Bid" Binding="{Binding Bid}" Width="*"/>
            <DataGridTextColumn Header="Ask" Binding="{Binding Ask}" Width="*"/>
            <DataGridTemplateColumn Header="Buy">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Content="BUY"
                                Click="OnBuyClicked"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
    <ListBox Grid.Row="2"
             ItemsSource="{Binding Orders}"/>
  </Grid>
</Window>

And finally, I have a bit of code behind this XAML to handle the BUY button clicks and initialize a viewmodel right in the view (note that this, and other practices outside of how to update the bid/ask on the collection of items may not be the best way to go about things, depending on how your application is going to grow).

public partial class MainWindow : Window
{
    private readonly CurrencyPairsViewModel _model = new CurrencyPairsViewModel();

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this._model;
    }

    private void OnBuyClicked(object sender, RoutedEventArgs e)
    {
        var pair = (CurrencyPair)((Button)sender).DataContext;
        this._model.Buy(pair);
    }
}

Hope the example is helpful!


Have you looked into the ObservableCollection?

Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.

This should only refresh those items that are changed, rather than the whole grid.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜