Search code examples
wpfdata-bindingdialogviewmodel

WPF: Instantiating a new window/dialog causes >unrelated< list binding to stop updating


SOLVED

My problem is a little weird and i'm not even sure why opening a window should have any effect at all on my list binding, so little hard to explain, so here is a video showing what happens: https://www.youtube.com/watch?v=bZ03l8OHEY4

I have 2 Pages, Start Page and Main Page. In Start Page user can select between (supported)devices detected on COM ports. When a device is clicked, the program reads the device and creates a view model list (asynchronously with BackgroundWorker) based on the data on the device, here i also instantiate and show a loading window/dialog. When clicking the device again the program then goes to Main Page and displays the view model list as a view list (with ItemsControl in xaml) that was created for this device.

So far everything works fine. However, if i have 2 devices a problem occurs if i do A and B but not when i do C. And A and B both works if i do not instatiate the loading window/dialog, i demonstrate this last in the youtube video(it also breaks if i instatiate but do not .Show()).

A) The sameway i said as above, then i go back to Start Page then reads and opens the other device. (this is the second thing i show in the youtube video)

B) The sameway i said as above, then i read and open the other device without going back to start page.

C) Instead, read both devices before opening any of them (stay on Start Page), then open the first device, then open the second device. (This is the first thing i show in the youtube video)

Top image is showing that the displayed list is the same as the list that is actually in the memory of the computer. Bottom image shows what happens when doing like described in B. As you can see the list on screen is different from what the LiveVisualTree says the list actually is.

When doing A/B the list that is displayed is not updated to the created view model list of the other device (the list of the first device is still shown). The view model list is created correctly (yes, i have double and triple checked) the view list is just not updated. Even if i manually use OnPropertyChanged() for the list.

However, when i do C everything works flawlessly. The view list is updated to display what the view model list contains.

Some code i think is relevant:

user clicks on device:

    public static void DeviceClick(ComPortVM comPort)
    {

        CurrentComPort = comPort;

        if (comPort.Device == null)
        {
            ShowReadingDeviceDialog(true);

            BackgroundWorker workThread = new BackgroundWorker();
            workThread.DoWork += new DoWorkEventHandler(BackgroundParameterReader_DoWork);
            workThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundNewInverter_RunWorkerCompleted);
            workThread.RunWorkerAsync(new ReadParameterArgs(basic, false));
        }
        else
        {
            OpenDevice();
        }
    }

OBS: when commenting out ShowReadingDeviceDialog(true); the problem does not occur and everything works

ShowReadingDeviceDialog(true);

    private static void ShowReadingDeviceDialog(bool show)
    {
        if (ReadingDeviceDialog != null)
        {
            ReadingDeviceDialog.Close();
        }

        if (show)
        {
            MainWindow.instance.IsEnabled = false;
            MainWindow.SetCursor(System.Windows.Input.Cursors.Wait);
            ReadingDeviceDialog = new OpeningInverterDialog();
            ReadingDeviceDialog.Owner = MainWindow.instance;
            ReadingDeviceDialog.Show();
        }
        else
        {
            MainWindow.SetCursor(System.Windows.Input.Cursors.Arrow);
            MainWindow.instance.IsEnabled = true;
        }
    }

OBS: when not instantiating a new ReadingDeviceDialog the problem does not occur and everything works

OpenDevice

    private static void OpenDevice()
    {
        if (!isOpening)
        {
            isOpening = true;
            if (!InfoPanels.ContainsKey(CurrentDevice))
            {
                InfoPanels.Add(CurrentDevice, new InfoPanelVM());
                InfoPanels[CurrentDevice].SetUp(CurrentDevice);
            }
            MainViewModel.instance.RefreshInfoPanel();

            startDelay.Interval = TimeSpan.FromMilliseconds(100);
            startDelay.Start();
        }
    }

startDelay Tick

    private static void StartDelay_Tick(object sender, EventArgs e)
    {
        MainWindow.OpenPage("MainPage");

        if (!paramTabBars.ContainsKey(CurrentDevice))
        {
            MainPage.ClearFrame();
            paramTabBars.Add(CurrentDevice, new ParamTabBarVM());
            ParamTabBar.SetUp(CurrentDevice);
        }
        else
        {
            ParamTabBar.OpenTab("");
        }

        startDelay.Stop();
        isOpening = false;
        ShowReadDeviceDialog(false);
        MainViewModel.instance.RefreshParamTabBtns();
    }

It's the ParamBarTab that contains the list that causes problems, called ParamTabBtns. Using MainViewModel.instance.RefreshParamTabBtns(); simply uses OnPropertyChanged(ParamTabBtns) for the currently selected Device, this fixed a similiar problem i had with the view list not updating to fit view model list. However this fix does not work now when i instantiate a loading dialog window.

Since the binding had worked all the time before i started instantiating a loading dialog i do not think the problem is in XAML, so i won't bother posting that to.

I know this question maybe weridly asked, but since i have no clue on what going on i don't really know what else information i should provide. I'm mostly just hoping for someone giving me ideas as what kind of thing that might possibly be the thing i should try to find, but right now i'm totally blind.

Edit: I'm starting to suspect it's a bug in WPF and not on my side? LiveVisualTree the DataContext gives me the right list, but that list is not shown on screen. Screen cap with green arrows is when everything works, with red arrows is when i did like in A. The list to the side that says 15 in length is the list that is supposed to be shown in the application, yet it isnt. Isn't LiveVisualTree supposed to show me the binding? If the binding works in LiveVisualTree isn't it supposed to work in the application too? Isn't that what LiveVisualTree is used for? Debugging? So shouldnt the bindings in LiveVisualTree reflect the actual bindings happening inside the application?

Edit 2: I tried BindingOperations.EnableCollectionSynchronization following http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux However, this had no impact on anything. So it probably isn't threading related either? And it doesnt make sense that it is threading related anyway, since i can change the collection as i please when the dialog window has not been intantiated in scenario A and B. And again, the dialog window has noting the do with the collection (no references, no method calls, no nothing) LiveVisualTree

EDIT 3, SOLUTION: Apparently when using

 <Window.DataContext>
     <vm:MainViewModel/>
 </Window.DataContext>

WPF creates a new instance of the datacontext, in the constructor of my MainViewModel i had static reference to itself. But when i instantiated a new window that reference got pointed to the newly created datacontext instead, which destroyed the bindings. To fix this i removed the code from above from all xaml files that had it and instead put this in the construction (code behind) DataContext = MainViewModel.instance;


Solution

  • Apparently when using

    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>
    

    WPF creates a new instance of the datacontext, in the constructor of my MainViewModel i had static reference to itself. But when i instantiated a new window that reference got pointed to the newly created datacontext instead, which destroyed the bindings. To fix this i removed the code from above from all xaml files that had it and instead put this in the construction (code behind):

    DataContext = MainViewModel.instance;