Home > OS >  Bind to ObsevableCollection not working in WINUI3 app
Bind to ObsevableCollection not working in WINUI3 app

Time:06-05

I am building a semi automatic administration platform to keep track of my daytrading. With a python script I can get information out of the platform and through a pipe it gets to the app who passes it into a SQLite database.

Now I'm working on getting the data out of the database and into the GUI of the application. Now, all 3 of the methods run on a Task thread as the pipe server is using a infinite while loop as well as the method that gets the data from the database. As the data is changing every 2 seconds it has to update the data in the GUI as well.

Binding from the xaml to the model is not working as the method is running on a different thread. This is where I'm stuck.

The code is as follows,

The first methods of the page are the ObservableCollection and the initialization which starts the Task methods with Task.Run(); as well as the PropertyChanged method.

        private ObservableCollection<OpenTradesModel> _trades = new ObservableCollection<OpenTradesModel>();
        public ObservableCollection<OpenTradesModel> Trades
        {
            get { return _trades; }
            set { _trades = value; OnPropertyChanged(nameof(Trades)); Debug.Print("Property Changed!"); }
        }
        
        public OpenTradesView()
        {
            InitializeComponent();
            List<Task> tasks = new()
            {
                Task.Run(() => { startServerAndUseData(); }),
                Task.Run(() => { runScript(); }),
                Task.Run(() => { DataBaseQueryToGUI(); }),
            };
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

The following method gets the data from the SQLite database and puts it into _trades.

public Task DataBaseQueryToGUI()
        {
            while (true)
            {
                try
                {
                    using var db = new DatabaseContext();
                    var rowData = db.OpenTrades.OrderBy(x => x.Id).ToList();

                    foreach (var row in rowData)
                    {
                        var query = new OpenTradesModel()
                        {
                            Ticket = row.Ticket,
                            Time = row.Time,
                            Type = row.Type,
                            Magic = row.Magic,
                            Identifier = row.Identifier,
                            Reason = row.Reason,
                            Volume = row.Volume,
                            PriceOpen = row.PriceOpen,
                            StopLoss = row.StopLoss,
                            TakeProfit = row.TakeProfit,
                            CurrentPrice = row.CurrentPrice,
                            Swap = row.Swap,
                            Profit = row.Profit,
                            Symbol = row.Symbol,
                        };
                        _trades.Add(query); //This is where the exception is thrown.
                        Debug.Print("Added to Model!");
                    }
                }
                catch (Exception e)
                {
                    Debug.Print(e.ToString());
                    break;
                }
               Thread.Sleep(2000);
            }
            return Task.CompletedTask;
        }

The xaml is as follows.

<ListView x:Name="ListData" MaxWidth="1496" Width="1496" Height="457" MaxHeight="457"
                          ItemsSource="{x:Bind Trades, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

                    <ListViewHeaderItem Margin="-11,0,0,0">
                        ...
                    </ListViewHeaderItem>

                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <Expander>
                                <Expander.Header>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            ...
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Grid.Column="0"  Text="{Binding Trades}"/>
                                        <TextBlock Grid.Column="1"  Text="{Binding Time}" Margin="-17,0,0,0"/>
                                        <TextBlock Grid.Column="2"  Text="{Binding Type}"/>
                                        <TextBlock Grid.Column="3"  Text="{Binding Magic}"/>
                                        <TextBlock Grid.Column="4"  Text="{Binding Identifier}"/>
                                        <TextBlock Grid.Column="5"  Text="{Binding Reason}"/>
                                        <TextBlock Grid.Column="6"  Text="{Binding Volume}"/>
                                        <TextBlock Grid.Column="7"  Text="{Binding PriceOpen}"/>
                                        <TextBlock Grid.Column="8"  Text="{Binding StopLoss}"/>
                                        <TextBlock Grid.Column="9"  Text="{Binding TakeProfit}"/>
                                        <TextBlock Grid.Column="10" Text="{Binding CurrentPrice}"/>
                                        <TextBlock Grid.Column="11" Text="{Binding Swap}" Margin="-15,0,0,0"/>
                                        <TextBlock Grid.Column="12" Text="{Binding Profit}" Margin="15,0,0,0"/>
                                        <TextBlock Grid.Column="13" Text="{Binding Symbol}"/>
                                    </Grid>
                                </Expander.Header>

The binding seems to work but does not show the data that is put into _trades. As the following exception shows it has to do with threading, I just have no idea how to get the data to the UI thread. Every answer I've found here didn't work for me.

System.Runtime.InteropServices.COMException (0x8001010E): The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))
   at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|20_0(Int32 hr)
   at WinRT.ExceptionHelpers.ThrowExceptionForHR(Int32 hr)
   at ABI.Microsoft.UI.Xaml.Interop.WinRTNotifyCollectionChangedEventArgsRuntimeClassFactory.CreateInstanceWithAllParameters(NotifyCollectionChangedAction action, IList newItems, IList oldItems, Int32 newIndex, Int32 oldIndex)
   at ABI.System.Collections.Specialized.NotifyCollectionChangedEventArgs.CreateMarshaler2(NotifyCollectionChangedEventArgs value)
   at ABI.System.Collections.Specialized.NotifyCollectionChangedEventHandler.NativeDelegateWrapper.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
   at System.Collections.ObjectModel.Collection`1.Add(T item)
   at TradeAdministration.Views.OpenTradesView.DataBaseQueryToGUI() in C:\Users\Nick\source\repos\TradeAdministration\TradeAdministration\Views\OpenTradesView.xaml.cs:line 96

Hopefully one of you can get me on the right track of thought. Thanks in advance!

CodePudding user response:

I fixed it by changing the DataBaseQueryToGUI() function as follows:

private async void DataBaseQueryToGUI() // Made it an async method.
        {
            await Task.Run(() => // Added
            { 
                while (true)
                {
                    try
                    {
                        using var db = new DatabaseContext();
                        var rowData = db.OpenTrades.OrderBy(x => x.Id).ToList();

                        foreach (var row in rowData)
                        {
                            var query = new OpenTradesModel()
                            {
                                Ticket = row.Ticket,
                                Time = row.Time,
                                Type = row.Type,
                                Magic = row.Magic,
                                Identifier = row.Identifier,
                                Reason = row.Reason,
                                Volume = row.Volume,
                                PriceOpen = row.PriceOpen,
                                StopLoss = row.StopLoss,
                                TakeProfit = row.TakeProfit,
                                CurrentPrice = row.CurrentPrice,
                                Swap = row.Swap,
                                Profit = row.Profit,
                                Symbol = row.Symbol,
                            };
                            this.DispatcherQueue.TryEnqueue(() => // Pushed these to the Dispatcher.
                            {
                                _trades.Add(query);
                                Debug.Print("Added to Model!");
                            });
                        }
                    }
                    catch (Exception e)
                    {
                        Debug.Print(e.ToString());
                        break;
                    }
                    Thread.Sleep(2000);
                }
            });
        }

This works for now. Although I am missing some data but that's fixable. Now I need to figure out a way to update the data instead of adding the new data.

  • Related