Custom Splash Screen: Image not Rendered - c#

I'm trying to create a Custom Spalsh Screen in WPF - Net 4.8 - C# to show a startup image with some messages that change during Loading process.
The Spalsh screen is in a separete Library. Here my code:
SPLASH SCREEN LIBRARY:
Window (xaml):
<Window ...
Title="SplashScreenWindow" Height="400" Width="610" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" Topmost="True" WindowStyle="None" >
<StackPanel>
<Image x:Name="imgSplash" Grid.Row="0" Source="{Binding ConfigModel.DisplayedImage}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
</Window>
Window (cs):
public SplashScreenWindow(string SplashImagePath)
{
InitializeComponent();
this.DataContext = new MySplashScreenViewModel(SplashImagePath);
}
SplashImagePath is the path to the Image. It is passed to the ViewModel. If it is null, default image should be loaded.
ViewModel:
public class SplashScreenConfigModel : ConfigModelBase
{
public BitmapImage DisplayedImage
{
get { return GetValue(() => this.DisplayedImage); }
set { SetValue(() => this.DisplayedImage, value);}
}
}
public class MySplashScreenViewModel : PropertyChangedNotificationBase
{
public MySplashScreenConfigModel ConfigModel
{
get { return GetValue(() => this.ConfigModel); }
set { SetValue(() => this.ConfigModel, value); }
}
public MySplashScreenViewModel(string SplashImagePath)
{
ConfigModel = new SplashScreenConfigModel();
if (SplashImagePath.IsEmpty()) SplashImagePath = "Media/DefaultImage.png";
ConfigModel.DisplayedImage = ImagesUtilities.LoadBitmapImageFromPath(SplashImagePath);
}
}
where MyImagesUtilities.LoadBitmapImageFromPath(SplashImagePath) is coded like that:
public static BitmapImage LoadBitmapImageFromPath(string path)
{
if (path.IsEmpty()) return null;
BitmapImage img = new BitmapImage();
img.DecodePixelWidth = 610;
img.BeginInit();
img.UriSource = new Uri(path, UriKind.Relative);
img.EndInit();
return img;
}
MAIN APP
The OnStartup is coded Like that:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SplashScreenWindow splash = new SplashScreenWindow("Media/AppImage.png");
this.MainWindow = splash;
Task.Factory.StartNew(() =>
{
splash.Dispatcher.Invoke(() => splash.Show());
//Various activities...
this.Dispatcher.Invoke(new Action(() =>
{
MainWindow main = new MainWindow();
this.MainWindow = main;
main.Show();
splash.Close();
}));
});
}
}
When I start the app, the splash screen appears, but no image is shown.
But if I:
Put a breakpoint at the end of the SetSplashScreen method
breakpoint at the end of the SetSplashScreen method
Insert img.Height in the watches:
img.Height in watches
And the I run the app, and, when breakpoint, is reached, click on "Continue", first one of this two errors occur:
Exception message
or
Errors
But after, by clicking again on Continue, the Splash Screen appears with the correct Image.
Correct Splash
Images (both deafult and app image) are a .png images with Compile Action = "Content" and Copy in output directory = "Copy if newer".
How can I correct that? Any idea?

Related

Using WPF UI element property value in ViewModel with ReactiveUI

I'm not really sure if this is a WPF or RxUI issue, so I just show my code and then explain my problem.
MainWindowView.xaml
<reactiveui:ReactiveWindow ...
Height="840"
Width="800"
ResizeMode="CanMinimize">
<DockPanel>
<!-- Some stuff in the main dock panel -->
<Border BorderThickness="5"
DockPanel.Dock="Top">
<DockPanel>
<!-- Other stuff in inner dock panel -->
<Grid Height="100"
Background="Black"
DockPanel.Dock="Top"
Margin="0 10 0 0">
<Image x:Name="UI_WaveformImage"
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Border}}, Path=Width}"
Stretch="Fill">
</Image>
</Grid>
</DockPanel>
</Border>
</DockPanel>
</reactiveui:ReactiveWindow>
MainWindoeView.xaml.cs
public MainWindowView ()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
this.WhenActivated(d =>
{
this.OneWayBind(ViewModel, vm => vm.Waveform.Image, v => v.UI_WaveformImage.Source).DisposeWith(d);
this.WhenAnyValue(x => x.UI_WaveformImage.ActualWidth).BindTo(this, x => x.ViewModel.WaveformWidth).DisposeWith(d);
});
}
MainWindowVieModel.cs
public class MainWindowViewModel : ReactiveObject, IEnableLogger
{
public ReactiveCommand<string, AudioWaveformImage> GenerateNewWaveform { get; }
private readonly ObservableAsPropertyHelper<AudioWaveformImage> _waveform;
public AudioWaveformImage Waveform => _waveform.Value;
public int WaveformWidth { get; set; }
public MainWindowViewModel ()
{
GenerateNewWaveform = ReactiveCommand.CreateFromTask<string, AudioWaveformImage>(p => GenerateNewWaveformImageImpl(p));
GenerateNewWaveform.ThrownExceptions.Subscribe(ex => {
this.Log().Error("GenerateNewWaveform command failed to execute.", ex);
});
_waveform = GenerateNewWaveform.ToProperty(this, nameof(Waveform), scheduler: RxApp.MainThreadScheduler);
(1) GenerateNewWaveform.Execute(AudioFilePath).ObserveOn(RxApp.MainThreadScheduler);
(2) this.WhenAnyValue(x => x.AudioFilePath)
.Where(p => !string.IsNullOrWhiteSpace(p))
.InvokeCommand(GenerateNewWaveform);
}
private async Task<AudioWaveformImage> GenerateNewWaveformImageImpl (string path)
{
UIOutput.AppendLine($"Waveform width start: {WaveformWidth}");
var wf = new AudioWaveformImage(WaveformWidth == 0 ? 100: WaveformWidth, 100, 100, Colors.CornflowerBlue, Colors.Red, path);
await Task.Delay(10);
//await wf.Update();
UIOutput.AppendLine($"Waveform width end: {WaveformWidth}");
return wf;
}
}
Just a bit of explanation:
UIOutput.AppendLine() just displays info in a textbox for me. I use it for debugging, but its purpose is to let the end-user know the progress of what's going on.
AudioWaveformImage is initialized with the desired size, colors, and path to the file that I want the waveform of. So, I need to know the size of the Image element I want to display it in.
So here's my problem:
The first time GenerateNewWaveformImageImpl() is executed on program startup, via line (1), UIOutput shows "Waveform width start: 0" and "Waveform width end: 0". After I change AudioFilePath and GenerateNewWaveformImageImpl() gets run a second time, via line (2), I get the correct width as UIOutput shows "Waveform width start: 776" and "Waveform width end: 776". Now I know I could hard code that, but that doesn't help when the window gets resized.
Why is the correct Width not being reported when the app first runs?
Try to invoke the command once the view model has been activated:
public class MainWindowViewModel : ReactiveObject, IEnableLogger, IActivatableViewModel
{
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public MainWindowViewModel()
{
this.WhenActivated((CompositeDisposable disp) =>
{
//invoke command...
});
}
}
Okay, the main problem in your original code is that you're invoking GenerateNewWaveform in the VM constructor. At this point, your Width has NOT been bound to your property yet, as witnessed here:
public MainWindowView () {
// Constructor happens first.
ViewModel = new MainWindowViewModel();
this.WhenActivated(d => {
// Binding to Width happens later..
this.WhenAnyValue(v => v.UI_WaveformImage.ActualWidth)
.BindTo(ViewModel, vm => vm.WaveformWidth)
.DisposeWith(d);
});
}
The answer that suggested using a ViewModelActivator will not work either, because ViewModel.WhenActivated is triggered before View.WhenActivated. I.e. Same issue as above. The command in the VM will be invoked before the Width binding in the View.
One way to solve this is to invoke your command after the Width binding.
public MainWindowView () {
ViewModel = new MainWindowViewModel(); // Constructor..
this.WhenActivated(d => {
// Bind to Width..
// Tell the VM to invoke, after Width is bound..
ViewModel.GenerateNewWaveform.Execute(...);
});
}
If you don't like having the View telling the VM what to do, because it's iffy, you could have the VM do WhenAnyValue(_ => _.WaveformWidth), filtering by > 0, and doing a Take(1). This will allow you to invoke when your VM receives the first non-zero width after the binding.

How to recenter UserControl(s) in Window

We have BaseDialogView with next xaml code:
<Window x:Class="Test.BaseDialogView"
Height="475"
WindowStartupLocation="CenterOwner"
SizeToContent="Height"
ResizeMode="CanResize"
SizeChanged="Window_SizeChanged">
<ContentControl Content="{Binding ContentPage}" />
</Window>
BaseDialogViewModel class:
public class BaseDialogViewModel : AbstractNotifyPropertyChangedItem
{
private UserControl contentPage;
public UserControl ContentPage
{
get { return this.contentPage; }
set
{
if (this.contentPage != value)
{
this.contentPage = value;
this.RaisePropertyChanged(() => this.ContentPage);
}
}
}
}
The usage is very simple:
BaseDialog dialog = new BaseDialog();
BaseDialogViewModel dialogVm = new BaseDialogViewModel();
dialog.Owner = Application.Current.MainWindow;
dialog.DataContext = dialogVm ;
dialogVm.ContentPage = new ActivationView();
dialogVm.ContentPage.DataContext = new ActivationViewModel();
So basically once you have an instance of BaseDialog, you just set ContentControl (by setting dialog.ContentPage and dialog.ContentPage.DataContext).
ActivationView is very simple. For example:
<UserControl x:Class="Test.ActivationView" d:DesignHeight="400" d:DesignWidth="700" MaxWidth="700">
<Grid> .... what ever you need
</UserControl>
The problem is that different UserControls windows are set, which have different width and height. When the first UserControl is shown it's place in the center of the MainWindow, which is ok. Then each new userControl is shown, but it's not centered. How do I center the BaseDialog window for each usercontrol?
I tried this (BaseDialogView):
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
Window w = sender as Window;
this.Top = (Application.Current.MainWindow.Height - w.ActualHeight) / 2;
}
but does not work ok (Some usercontrols are still not pixel centered). I also tried adding this to BaseDialogView Xaml
<Window .... VerticalAlignment="Center">
but it seems to be working only for initial instance.
First of you should really consider propperly implementing the MVVM pattern.
It will make your live easier, also instead of centering the element manually in the size change event you should set its owner and WindowStartupLocation by using
Window win = new Window();
win.Content = new MyUserControl();
win.Owner = this;
win.WindowStartupLocation = WindowStartupLocation.CenterOwner;
Instead of having one window where you keep changing the content i would consider having different windows..but that may vary on your specific case

c# wpf MediaElement black flash

When MediaElement starts playing video it shows like a black frame for a moment. Here is the code:
<Window x:Class="MediaElementTest.MainWindow" /* */
Title="MainWindow" Height="350" Width="525" Background="Red">
<Grid>
<ContentControl x:Name="contentControl"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
NextState();
}
public void NextState()
{
var content = new VideoState();
contentControl.Content = content;
}
}
And
<UserControl x:Class="MediaElementTest.VideoState" /* */
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<MediaElement x:Name="videoPlayer" MediaEnded="videoPlayer_MediaEnded" LoadedBehavior="Manual" />
</Grid>
</UserControl>
public partial class VideoState : UserControl
{
public VideoState()
{
InitializeComponent();
videoPlayer.Source = new Uri("C:\\wpf\\bin\\Debug\\data\\start.mp4");
//videoPlayer.Position = TimeSpan.FromMilliseconds(100); //!!! WORKS FINE WITH IT
videoPlayer.Play();
}
private void videoPlayer_MediaEnded(object sender, RoutedEventArgs e)
{
videoPlayer.Source = null;
videoPlayer = null;
GC.Collect();
MainWindow wnd = (MainWindow)Application.Current.MainWindow;
wnd.NextState();
}
}
If I set videoPlayer.Position to like 100 milliseconds it works fine. How can I get rid of this black frame. I've tried to set ScrubbingEnabled="true"and do something like :
videoPlayer.Play();
videoPlayer.Pause();
videoPlayer.Play();
But there is no difference and black popup still occurs. If I set videoPlayer.Position to 0ms at mediaEnded event and play it works also fine. I would appreciate any help.
Get the Loaded event of your User Control and move the below code in the handler of Loaded event in your User control's code behind file
videoPlayer.Source = new Uri("C:\\wpf\\bin\\Debug\\data\\start.mp4");
videoPlayer.Play();
Once the VideoState user control is properly loaded as the content of your window, only then you execute further logic to play the video. I hope that it will solve the black screen problem.

ApplicationBar with minimized mode override WebBrowser component at landscape orientation

i have been read some similar questions on Stackoverflow but cannot make it works correctly
Here is my WebBrowser XAML code
<phone:WebBrowser Name="webBrowser"
ScriptNotify="webBrowser_ScriptNotify"
IsScriptEnabled="True"
Margin="0"
Background="Transparent"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
and Application Bar in code
void BuildAppBar()
{
ApplicationBar = new ApplicationBar();
ApplicationBar.Mode = ApplicationBarMode.Minimized;
...
}
This happens when you have set the Mode Property of ApplicationBar to Minimized (i don't know why), so what you can do is to use OrientationChanged event of the page to set the Mode Property:
private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
{
if (e.Orientation == PageOrientation.PortraitUp)
{
this.ApplicationBar.Mode = ApplicationBarMode.Minimized;
}
else
{
this.ApplicationBar.Mode = ApplicationBarMode.Default;
}
}

Windows Phone 8 Camera hangs when not in Debug/Master mode

This is a problem that has been bugging me a lot. I still haven't got a solution for this.
I have this XAML code, the DrawingSurfaceBackgroundGrid is an Unity game, and the rest is collapsed Camera.
<DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
<!-- Augmented Reality -->
<Canvas VerticalAlignment="Center" x:Name="arCameraStack" Canvas.ZIndex="1" Width="732" Height="549" HorizontalAlignment="Left" Visibility="Collapsed" Tap="viewfinderCanvas_Tap">
<Canvas.Background>
<VideoBrush x:Name="viewfinderBrush" />
</Canvas.Background>
<Image x:Name="imgTarget" Source="/Assets/Icons/camera.target.png" VerticalAlignment="Center" HorizontalAlignment="Center" Canvas.Left="114" Canvas.Top="27"/>
</Canvas>
</DrawingSurfaceBackgroundGrid>
These are my Start/Stop camera functions:
public void StartCamera(bool hasTarget)
{
camera = new PhotoCamera(CameraType.Primary);
viewfinderBrush.SetSource(camera);
Dispatcher.BeginInvoke(() =>
{
if (hasTarget)
{
imgTarget.Visibility = Visibility.Visible;
}
else
{
imgTarget.Visibility = Visibility.Collapsed;
}
});
}
public void StopCamera()
{
if (camera != null)
{
camera.Dispose();
camera = null;
}
}
I have a screen system that changes the views according to what you want to see, sorta like changing pages.
This gets called in the page that shows the Camera part.
public void Show()
{
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.arCameraStack.Visibility = Visibility.Visible;
});
MainPage.Instance.StartCamera(false);
}
And this gets called when I want to hide the Unity part.
public void Hide()
{
UnityApp.SetNativeResolution(0, 0);
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
Everything works when I have the solution build and run through Visual Studio, but once I start the app from the phone, not in Master/Debug mode from VS, the camera just hangs. I found out that when I toggle the visibility of anything in the page, the camera will update for a second, and then hang once again.
Well, I found out what was the problem.
My Show/Hide functions look like these now:
public void Hide()
{
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(null);
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(null);
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Visible;
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
public void Show()
{
var content = Application.Current.Host.Content;
var width = (int)Math.Floor(content.ActualWidth * content.ScaleFactor / 100.0 + 0.5);
var height = (int)Math.Floor(content.ActualHeight * content.ScaleFactor / 100.0 + 0.5);
UnityApp.SetNativeResolution(width, height);
UnityApp.UnObscure();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(UnityApp.GetBackgroundContentProvider());
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(UnityApp.GetManipulationHandler());
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Collapsed;
MainPage.Instance.ApplicationBar.IsVisible = false;
});
}
The main problem was in the DrawingSurfaceBackground.SetBackgroundContentProvider and DrawingSurfaceBackground.SetBackgroundManiuplationHandler functions.
Now switching between the Unity part and WP part is easy.

Categories