1

In the main page <Home_Page> \

<ContentPage.BindingContext>
  <local:ExcelDlj x:Name="viewModel" />
</ContentPage.BindingContext>
<ContentPage.Content>
  <syncfusion:SfDataGrid x:Name="sfDataGrid"
                         ColumnWidthMode="FitByCell"
                         AllowTriStateSorting="True"                 
                         SortingMode="Single"
                         GridLinesVisibility="Both"
                         ItemsSource="{Binding Clients_Colection}"  
                         SelectionMode="SingleDeselect"
                         CellTapped="sfDataGrid_CellTapped" />                                        

Creates a table the Excel_Dlj


namespace M4.View_Model
{
    
    public class ExcelDlj 
    {
        public  ObservableCollection<Client> Clients_Colection { get; set; }
       
        public string Head_Table { get; set; }


        public ExcelDlj()
        {
            Clients_Colection = new ObservableCollection<Client>();
            
            Add_Clients();
        }

        public  void Add_Clients()
        {
            using ExcelEngine excelEngine = new();
            Syncfusion.XlsIO.IApplication application = excelEngine.Excel;
            application.DefaultVersion = ExcelVersion.Xlsx;

            string desktopPaht2 =  Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            string filePath = Path.Combine(desktopPaht2, "m.xlsx");


            FileStream inputStream = new(filePath, FileMode.Open, FileAccess.Read);

            IWorkbook workbook = application.Workbooks.Open(inputStream);
            IWorksheet worksheet = workbook.Worksheets[0];

var row1 = worksheet.Rows[0];
            
            Head_Table = (Convert.ToDouble(row1.Cells[3].DisplayText)).ToString("N1", CultureInfo.CreateSpecificCulture("sv-SE"));

            for (int R = 1; R < worksheet.Rows.Length; R++)
            {
                var row = worksheet.Rows[R];
                var Cl = new Client(row.Cells[0].DisplayText, row.Cells[1].DisplayText,
                                                 row.Cells[2].DisplayText, row.Cells[3].DisplayText,
                                                 row.Cells[4].DisplayText, row.Cells[5].DisplayText,
                                                 row.Cells[6].DisplayText, row.Cells[7].DisplayText);
                Clients_Colection.Add(Cl);
            }
            inputStream.Dispose();
        }
    }
}

class property is taken as the source.

What I tried

Thread thread = new(() => { Add_Clients(); });
thread.Start();

And here's the exception:

System.Runtime.InteropServices.COMException: "The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))"`.

And in the end, why do I need this Excel file is large enough has about 350 thousand cells with formulas and has 10 pages. Each page should be loaded separately in the ObservableCollection Clients_Collection which is located in the main thread.

If I'm doing something wrong, please tell me about it.

Mafii
  • 7,227
  • 1
  • 35
  • 55
  • My answer gives you a technical solution. The software engineering solution would be to use data binding onto a collection and push your elements into that (please make it thread safe, e.g. with ImmutableInterlocked.Add on immutable lists!) collection. – Mafii Dec 20 '22 at 10:11
  • Instead of trying to access the UI from another thread, break your code into processing and UI parts and execute the processing parts with `await Task.Run(...);`. After `await` you'll be back in the UI thread. You'll have to change `AddClient` to an `async Task` method – Panagiotis Kanavos Dec 21 '22 at 08:11

2 Answers2

1

Easy, but not optimal: Use MainThread.InvokeOnMainThreadAsync and pass the code you want to run. This concept is called a Dispatcher, and is widely used across many UI frameworks, be it Java or .NET!

I think it was Device.InvokeOnMainThreadAsync once, but the docs say it's MainThread.InvokeOnMainThreadAsync now.

Example:

await MainThread.InvokeOnMainThreadAsync(async () => // do ui stuff);

Here are the docs:

https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/appmodel/main-thread?view=net-maui-7.0


Best practice is to use the dispatcher of the UI object you have in scope to avoid problems with multi-window apps.

This should be done in your code behind anyways, not in a view model - MVVM is very clear about this.

You'll best read it from ToolmakerSteve who explains the difference very well and adds some good context:

https://stackoverflow.com/a/74805342/5962841

Mafii
  • 7,227
  • 1
  • 35
  • 55
  • LIMITATION: ONLY use `MainThread` (or the equivalent `Application.Current.Dispatcher`) if app is single window. On a Desktop App, if app has multiple windows, the other thread will need to be told which window's Dispatcher to use. See [tl;dr: "best practice" is to call Dispatcher on some UI object](https://stackoverflow.com/a/74805342/199364) for way too much detail about the THREE ways to get to a Dispatcher. – ToolmakerSteve Dec 21 '22 at 01:14
  • @ToolmakerSteve absolutely correct, I wasn't very precise. I'll add additional information. – Mafii Dec 21 '22 at 08:04
  • 1
    @ToolmakerSteve great answer by the way, no need for me to re-write what was already written nicely! – Mafii Dec 21 '22 at 08:09
0

It seems you are trying to export the Excel sheet's data to the DataGrid as an ItemsSource. Achieved this requirement in the simple sample by using the using statement. I could run the application without any exceptions. You can set the DataGrid's ItemsSource property as an ObservableCollection or DataTable. I have explained both ways in the code snippets. Please find the code snippets below.

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:datagrid="clr-namespace:Syncfusion.Maui.DataGrid;assembly=Syncfusion.Maui.DataGrid"
             x:Class="SfDataGridDemo.MainPage">

    <datagrid:SfDataGrid x:Name="datagrid" />

</ContentPage>
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        SetItemsSource();
    }

    void SetItemsSource()
    {
        using (ExcelEngine excelEngine = new ExcelEngine())
        {
            Syncfusion.XlsIO.IApplication application = excelEngine.Excel;
            application.DefaultVersion = ExcelVersion.Xlsx;

            string basePath = System.AppDomain.CurrentDomain.BaseDirectory.Split("bin").First();

            // Used different excel document. So, please download the demo application and check or modify these code changes as per your requirement.
            FileStream inputStream = new FileStream(Path.Combine(basePath, "Data/DataGrid.xlsx"), FileMode.Open, FileAccess.Read);
            IWorkbook workbook = application.Workbooks.Open(inputStream);
            IWorksheet worksheet = workbook.Worksheets[0];

            // Approach 1: Export excel data as an `ObservableCollection`.
            //
            ObservableCollection<OrderInfo> employees = new ObservableCollection<OrderInfo>();
            for (int i = 1; i < worksheet.Rows.Length; i++)
            {
                var row = worksheet.Rows[i];
                employees.Add(new OrderInfo() { ProductNo = row.Cells[0].DisplayText, DealerName = row.Cells[1].DisplayText, ShippedDate = row.Cells[2].DisplayText, ShipCountry = row.Cells[3].DisplayText, ShipCity = row.Cells[4].DisplayText, Price = row.Cells[5].DisplayText });
            }
            datagrid.ItemsSource = employees;


            // Approach 2: Export excel data as a `DataTable`.
            //
            // DataTable customersTable = worksheet.ExportDataTable(worksheet.UsedRange, ExcelExportDataTableOptions.ColumnNames);
            // datagrid.ItemsSource = customersTable;

            inputStream.Dispose();
        }
    }
}

Demo application

Ashok Kuvaraja
  • 666
  • 3
  • 11