简体   繁体   中英

WPF Frame.Navigate triggering multiple events

I'm a fairly new to WPF and C#. I have a frame component on my main window and 4 buttons next to it that navigate to different views in the frame. Within one the the views there is a DataGrid that has a SelectionChanged event which makes an SQL call to a database that fetches records, whose data is then used to populate a list of custom objects (these relate to the selected item on the DataGrid). Anyways, the problem I have is that from time to time multiple calls (2 or 3) to the SelectionChanged event are being triggered at the same time for a single selection change (mouseclick) on the DataGrid.

The navigation button click events on the main window all look like this:

    private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
    {
        MainFrame.Navigate(new Uri("/Views/MyDeskView.xaml", UriKind.Relative));
    }

    private void btn_AllOrders_Click(object sender, RoutedEventArgs e)
    {
        MainFrame.Navigate(new Uri("/Views/AllOrdersView.xaml", UriKind.Relative));
    }

After some experiementation, I've found that the bug only happens after changing views away from the view with the DataGrid, and then changing back to it (but not always). When the bug appears the number of calls generally corresponds to the number of times I had switched views. Furthermore, the bug will simply vanish if leave the program alone for a minute or two. This makes me suspect that there multiple instances of the DataGrid view lingering like ghosts in memory and duplicating event calls until they are cleaned up by a garbage collector.

Should I be cleaning something up each time I switch views, or am I looking in the wrong place?

Thank you in advance for any help.

Edit: In answer to @Peter Moore I subscribe to the event in the DataGrid declaration within the views XAML: SelectionChanged="dtg_MyDeskOrderGrid_SelectionChanged"

Edit: This is the sequence that happens on a selection change in the data grid. It includes several UI changes while the SQL records for the new selection are retrieved and displayed on a second DataGrid (dtg_MyDeskOrderItems). While the SQL call is being made, the relevant controls are disbaled and a semi-transparent panel (bdr_DGLoadingPanel) is moved on screen to cover them and display a loading animation. When the work is done, the work area is re-enabled and the loading panel moved off screen. Focus is also returned to the main "order" Datagrid.

dtg_MyDeskOrderGrid: This is the main DataGrid showing all "Orders"

dtg_MyDeskOrderItems: This is a secondary DataGrid that is updated to show all "Items" in the selected order.

    private void dtg_MyDeskOrderGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        CurrSelectedOrder = (Order_class)dtg_MyDeskOrderGrid.SelectedItem;
        if (CurrSelectedOrder.ItemList == null) 
        {
            if (NowWorking == false)
            {
                NowWorking = true;
                bdr_DGLoadingPanel.Margin = new Thickness(2);
                dtg_MyDeskOrderGrid.IsEnabled = false;
                bdr_FilterPanel.IsEnabled = false;
                bdr_DGLoadingPanel.Focus();
                img_LoadingCircle.RenderTransform = rt;
                img_LoadingCircle.RenderTransformOrigin = new Point(0.5, 0.5);
                da.RepeatBehavior = RepeatBehavior.Forever;
                rt.BeginAnimation(RotateTransform.AngleProperty, da);
                bdr_DGLoadingPanel.UpdateLayout();
                worker.RunWorkerAsync();
            }
        }
        else
        {
            dtg_MyDeskOrderItems.ItemsSource = null;
            dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
            dtg_MyDeskOrderItems.Items.Refresh();
        }
    }

    private void worker_DoWork(object? sender, DoWorkEventArgs e)
    {
        DatabaseConnection DBConn9 = new DatabaseConnection();
        DBConn9.FillOrderItems(CurrSelectedOrder);
    }

    private void worker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
    {
        this.Dispatcher.Invoke(() =>
        {
            dtg_MyDeskOrderItems.ItemsSource = null;
            dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
            dtg_MyDeskOrderItems.Items.Refresh();
            bdr_DGLoadingPanel.Margin = new Thickness(1000, 2, 2, 2);
            rt.BeginAnimation(RotateTransform.AngleProperty, null);
            dtg_MyDeskOrderGrid.IsEnabled = true;
            bdr_FilterPanel.IsEnabled = true;
            // The following work-around and accompanying GetDataGridCell function were used to give keyboard focus back to the datagrid to make navigation with arrow keys work again.
            // It appears keyboard focus is not returned to the Datagrid cells when using the Datagrid.focus() method.
            Keyboard.Focus(GetDataGridCell(dtg_MyDeskOrderGrid.SelectedCells[0]));
            NowWorking = false;
        });
    }

Edit... Following the advice of the commentors, I was able to fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:

private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
    {
        dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
    }

However, I was not able to reproduce the bug using a barebones testing project.

The UI in the original project is quite heavy, so I'm wondering if it's not the old view and events lingering in memory as this seems to fit the behavior of the bug & fix (Only occuring when I navigate away and back causing a new view to be created, multiple event triggers corresponding to the number of times I navigated away, and then finally the bug vanishing of its own accord after a moment or two).

I won't be settling on this as a final solution and instead will learn about ways I can reuse instances of my views (as suggested by Bionic) instead of recreating them. The reason for this is, if the SelectionChanged event is getting multiple triggers from old view instances, then it is likely other events will suffer from the same bug. This would be bad.

@BionicCode If you are still around, could you repost your initial comment as a solution so I can mark it answered?

Thank you to everyone for all the help and education. ^_^

Following the advice of the commentors, I was able to initially fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:

private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
    {
        dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
    }

However as this solution only disconnected the one event and didn't solve the root problem of old views lingering in the background and firing events before being cleaned up, I finally went the following code that keeps only one instance of my view in memory and reuses it.

private MyDeskView? currMyDeskView = null;

private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
    {
        if (currMyDeskView == null)
        {
            currMyDeskView = new MyDeskView();
        }
        MainFrame.Navigate(currMyDeskView);
    }

Thank you to everyone who helped me get over this bug.

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