0

I write my first WPF application, which consist of several pages:

  1. Welcome page with some logo
  2. Login page with login form
  3. Main page with account info

MainWindow contains <Frame> WPF Control, and I use animation to show next/previous page.

I write my own MainAnimation class to perform animation.

This application works fine on my laptop, but when I try to run it on the machine of my friend animation just do nothing.

I think that trouble related with Dispatcher.Invoke() method calling, and I tried to find solution over the web (here here here and here) and I tried:

  • use Application.Current.Dispatcher
  • use Dispatcher.BeginInvoke() instead of Dispatcher.Invoke()

but it does nothing.

So, I show Welcome page only 2 seconds and Login page must loaded automatically.

This is the code of WelcomePage.xaml.cs file:

public partial class WelcomePage : Page {
    public WelcomePage (MainWindow parent) {
        InitializeComponent();
        this.parent = parent;

        Task.Factory.StartNew(() => ShowLoginForm());
    }

    private MainWindow parent;

    private void ShowLoginForm()
    {
        Thread.Sleep(2000);
        this.parent.GoToLoginForm();
    }

}

This is the code of MainWindow.xaml.cs file:

public partial class MainWindow : Window {
    public MainWindow () {
        InitializeComponent();

        animation = new MainAnimation(this, this, Main, new WelcomePage(this));
    }

    private MainAnimation animation;

    public void GoToLoginForm() => animation.ShowNextPage(new LoginPage(this));
    public void GoToVideosForm() => animation.ShowNextPage(new MainPage(this));

}

And this is related parts on MainAnimation class (MainAnimation.cs):

public class MainAnimation 
{
    public MainAnimation(FrameworkElement resourcesOwner, DispatcherObject dispatcherOwner, Frame currentPageContainer, Page firstPage)
    {
        this.resourcesOwner = resourcesOwner;
        this.dispatcherOwner = dispatcherOwner;
        this.currentPageContainer = currentPageContainer;

        pages = new Stack<Page>();
        pages.Push(firstPage);

        currentPageContainer.Content = pages.Peek();
    }

    private Stack<Page> pages;
    private FrameworkElement resourcesOwner;
    private DispatcherObject dispatcherOwner;
    private Frame currentPageContainer;

    private void ShowPageForward()
    {
        dispatcherOwner.Dispatcher.Invoke((Action)delegate {
            if (currentPageContainer.Content != null)
            {
                var page = currentPageContainer.Content as Page;
                if (page != null)
                {
                    page.Loaded -= NextPage_Loaded;
                    UnloadPageForward(page);
                }
            }
            else
            {
                LoadPageForward();
            }
        });
    }

    private void UnloadPageForward(Page page)
    {
        Storyboard sb = (resourcesOwner.FindResource("SlideForwardOut") as Storyboard).Clone();
        sb.Completed += StoryboardForward_Completed;
        sb.Begin(currentPageContainer);
    }

    private void StoryboardForward_Completed(object sender, EventArgs e)
    {
        LoadPageForward();
    }

    private void LoadPageForward()
    {
        pages.Peek().Loaded += NextPage_Loaded;
        currentPageContainer.Content = pages.Peek();
    }

    private void NextPage_Loaded(object sender, RoutedEventArgs e)
    {
        Storyboard sb = resourcesOwner.FindResource("SlideForwardIn") as Storyboard;
        sb.Begin(currentPageContainer);
    }

}

I'm new with WPF and may be just don't understand some details, so I will be happy if you help me to solve this small but very offensive issue.

Update #1: software versions

  • OS for development: Windows 10 x64
  • OS for test: Windows 8.1 x64
  • VS version: Visual Studio 2017 Community Edition
  • Application target framework: v.4.5
V. Panchenko
  • 774
  • 1
  • 10
  • 32
  • 1
    Why are you calling ShowLoginForm on a background thread? WPF controls have thread affinity. And why do you sleep? – mm8 Aug 09 '17 at 09:49
  • I think that application will looks like "not responding" if I do not call `ShowLoginForm` on a background thread. But how can I do another way? – V. Panchenko Aug 09 '17 at 09:51
  • What if you simply remove Task.Factory.StartNew and Thread.Sleep? – mm8 Aug 09 '17 at 09:52
  • I need `Thread.Sleep()` to show `Welcome page` for 2 seconds. This page as annotation to my application, it contains logo and application title. Do you know another way to show this page for 2 seconds? – V. Panchenko Aug 09 '17 at 09:53

1 Answers1

2

Since WPF controls have thread affinity it doesn't make much sense to create them on a background thread in most cases.

If you want to wait for 2 seconds before you show the login page, you could either use a DispatcherTimer or wait asynchronously:

public partial class WelcomePage : Page
{
    public WelcomePage(MainWindow parent)
    {
        InitializeComponent();
        this.parent = parent;

        ShowLoginForm();
    }

    private MainWindow parent;

    private async void ShowLoginForm()
    {
        await Task.Delay(2000);
        this.parent.GoToLoginForm();
    }
}

Then you won't need any calls to Dispatcher.Invoke.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks for an advice: I trying to use DispatcherTimer in my application, but I got `System.Windows.Markup.XamlParseException` inside constructor of class `LoginPage`, and I think this exception occurs because I create `new LoginPage(this)` (inside `MainWindow.GoToLoginForm()` method) not from STA thread (but from thread created for `DispatcherTimer_Tick()`. – V. Panchenko Aug 09 '17 at 10:16
  • DispatcherTimer_Tick fires on then UI thread. What about the sample code that I posted? You should not create any background threads here. – mm8 Aug 09 '17 at 10:18
  • Also `System.Windows.Markup.XamlParseException` but another stack trace. Note that exception throws only on the testing OS. If I run this application on my laptop it works fine. Maybe it helps. I can attach both stack traces. – V. Panchenko Aug 09 '17 at 10:25
  • What does inner exception say? – mm8 Aug 09 '17 at 10:25
  • I can't see inner exception, because I retrieve this exception from Event Viewer tool (my application does nothing, just show "application was stopped" message). – V. Panchenko Aug 09 '17 at 10:26
  • `MainAnimation` class still use `Dispatcher.Invoke()` method. Did you see this class? – V. Panchenko Aug 09 '17 at 10:28
  • Thank you so much, @mm8 for your code with async/await! Last exception occurs because I use full path to some image when I must use relative path!) – V. Panchenko Aug 09 '17 at 10:36