0

I am new to C# WPF programming and working on a WPF-Application that has to include a legacy ActiveX component. I know ActiveX is kind of dead but I have to use it in this case unfortunately. I use MVVM Light with an IFrameNavigationService as shown in this post.

Furthermore I used this example to bind the ActiveX component into the view to follow the guidlines of MVVM which is probalby not the best way in my opinion.

On each navigation I have to wait until page is loaded to connect with the ActiveX component (VideoService). The connecting operation takes about 5 seconds. So the user has to wait a long time to use the page which is obviously bad.

Now I got two questions:

  1. How would you include an ActiveX component into the MVVM pattern? Unfortunately I need the VideoService to stream a video given by the VideoService in the View and I need the VideoService to request data of the video in the ViewModel.

  2. How can I use the ActiveX component on every page without (un)mounting it on every page change?

Paint

View/MainWindows.xaml

<Window x:Class="View.Desktop.MainWindow"
        ...
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding WindowLoadedCommand }" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
    </Grid>
</Window>

ViewModel/MainViewModel.cs

{
    public class MainViewModel : ViewModelBase
    {
        private readonly IFrameNavigationService navigationService;

        public RelayCommand WindowLoadedCommand { get; private set; }

        public MainViewModel(IFrameNavigationService navService)
        {
            navigationService = navService;

            WindowLoadedCommand = new RelayCommand(() =>
            {
                navigationService.NavigateTo("Home");
            });
        }
    }
}

View/Pages/Home.xaml

<Page x:Class="View.Desktop.Pages.HomePage"
      ...
      DataContext="{Binding HomeViewModel,Source={StaticResource Locator}}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding PageLoadedCommand }" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <ContentControl Content="{Binding VideoWindowsFormsHost}" />
        <Button Content="Navigate" Command="{Binding NavigateCommand}" />
    </Grid>
</Page>

ViewModel/HomeViewModel.cs

{
    public class HomeViewModel : ViewModelBase
    {
        private readonly IFrameNavigationService navigationService;

        readonly VideoService video;

        public WindowsFormsHost VideoWindowsFormsHost
        {
            get { return new WindowsFormsHost() { Child = video.v }; }
        }

        public ICommand PageLoadedCommand { get; private set; }
        public ICommand NavigateCommand { get; private set; }

        public HomeViewModel(IFrameNavigationService navService)
        {
            navigationService = navService;

            video = new VideoService();

            PageLoadedCommand = new RelayCommand(async () =>
            {
                // Very slow
                video.connect();
            });

            NavigateCommand = new RelayCommand(() =>
            {
                video.Disconnect();
                navigationService.NavigateTo("Settings");
            });
        }
    }
}

View/Pages/Settings.xaml

<Page x:Class="View.Desktop.Pages.SettingsPage"
      ...
      DataContext="{Binding SettingsViewModel,Source={StaticResource Locator}}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding PageLoadedCommand }" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <ContentControl Content="{Binding VideoWindowsFormsHost}" />
        <Button Content="Navigate" Command="{Binding NavigateCommand}" />
    </Grid>
</Page>

ViewModel/SettingsViewModel.cs

{
    public class SettingsViewModel : ViewModelBase
    {
        private readonly IFrameNavigationService navigationService;

        readonly VideoService video;

        public WindowsFormsHost VideoWindowsFormsHost
        {
            get { return new WindowsFormsHost() { Child = video.v }; }
        }

        public ICommand PageLoadedCommand { get; private set; }
        public ICommand NavigateCommand { get; private set; }

        public SettingsViewModel(IFrameNavigationService navService)
        {
            navigationService = navService;

            video = new VideoService();

            PageLoadedCommand = new RelayCommand(async () =>
            {
                // Very slow
                video.connect();
            });

            NavigateCommand = new RelayCommand(() =>
            {
                video.Disconnect();
                navigationService.NavigateTo("Home");
            });
        }
    }
}
tomole
  • 734
  • 2
  • 10
  • 28
  • 1
    _"Furthermore I used this example .... which is probalby not the best way in my opinion"_ - agreed. The poster there thought he was trying to avoid something he assumed was a violation of MVVM (which it wasn't) only to break it by having the ViewModel know about the view. All that is needed is to perform the switching logic in the _code-behind_ triggered by whatever works for the reader. It is a common fallacy that MVVM somehow implies _"no code-behind **ever**"_. A _view is a view is a view irrespective of whether it is C# or XAML_ – MickyD May 29 '20 at 09:08
  • 1
    Even though ActiveX controls are UI, some can be marked as multi-threaded apartment (MTA) meaning you can instantiate it in one thread and invoke it from multiple threads. Have you tried calling `connect()` from a child thread? This could potentially mean it could connect in the **background** – MickyD May 29 '20 at 09:14
  • Thanks for your detailed reply. Your opinion sounds reasonable. I tried something like `Task.Run(() => video.connect());` At least the connect call is not blocking anymore. But since almost every input depends on that connection the user has to wait (5 seconds) until the connection was successful to use the view. – tomole May 29 '20 at 09:26
  • 1
    No problem, but now that the connection is happening in the background the UI should no longer be frozen. Also, you could try placing the ActiveX in a common view belonging to the main window so that it is only initialised once – MickyD May 29 '20 at 09:31
  • I thought about something similar. But I could not find something about having a second view in application runtime and mount it conditionally into the MainView. – tomole May 29 '20 at 09:38
  • See what you can find or maybe ask that as a new question? I think your question above is essentially _how to handle a control that takes a long time to load_ rather than being explicitly about an ActiveX. (Other controls can take a long time too. e.g. web views). It sounds like it has been mitigated now with the `Task.Run`? Good luck :) – MickyD May 29 '20 at 10:02
  • "_Other controls can take a long time too. e.g. web views_" Thanks for that advice. I will give it a try. – tomole May 29 '20 at 10:16
  • 1
    Not a problem good sir – MickyD May 29 '20 at 10:39

0 Answers0