WPF: dispatcher.invoke do not running - c#

I write my first WPF application, which consist of several pages:
Welcome page with some logo
Login page with login form
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

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.

Related

Xamarin.iOS AddObserver not firing for change event for udpate to UIView.Window

I'm trying to implement a lifecycle effect in Xamarin.Forms, but am having trouble for the iOS version. For some reason, I can't seem to observe the window changing notification event. Below is my code:
public class CustomLifeCycleEffectRouter : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.New;
UIView? _nativeView;
CustomLifeCycleEffect? _lifeCycleEffect;
IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_lifeCycleEffect = Element.Effects.OfType<CustomLifeCycleEffect>().FirstOrDefault() ?? throw new ArgumentNullException($"The effect {nameof(CustomLifeCycleEffect)} can't be null.");
_nativeView = Control ?? Container;
_isLoadedObserverDisposable = _nativeView?.AddObserver("window", ObservingOptions, isWindowAttachedObserver);
}
protected override void OnDetached()
{
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
_isLoadedObserverDisposable.Dispose();
}
private void isWindowAttachedObserver(NSObservedChange nsObservedChange)
{
if (_nativeView.Window != null)
_lifeCycleEffect?.RaiseLoadedEvent(Element);
else
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
}
}
I am well aware that the Xamarin.Community Toolkit has a similar effect, but it fires the event to early; I need it to fire when I can navigate up the hiearchy to the root parent. Can anybody see a problem?
Edit
I've created a small sample to replicate my behaviours and issues. It can be viewed here:
https://github.com/sonic1015/LifeCycleEffectTesting
The goal is to only have the following messages in the debug output:
$"{elementName} is already a page."
$"{elementName} is a child of {pageName}."
and NOT these ones:
$"{elementName} does not have a parent ???."
$"How can {elementName} be loaded and not have a parent in hierarchy ???."
$"WTF??? we never loaded {elementName}."
These messages can be found in the "ViewExtensions" class, and I've the goal is to have every user-created view fire off good messages.
One thing I've noticed:
I also included a variant of the Xamarin Community Toolkit version of the router effect in the platform project, and it actually works, with the exception that it seems if any views are templated, it will fire "loaded" when it does not yet have a parent. I think this is why it originally didn't work for me, so if I could figure out a way to work that little edge case out, I could use that version of the routing effect.
1.Create a ViewLifecycleEffect class that implements RoutingEffect in the shared project like below:
public class ViewLifecycleEffect : RoutingEffect
{
public const string EffectGroupName = "XFLifecycle";
public const string EffectName = "LifecycleEffect";
public event EventHandler<EventArgs> Loaded;
public event EventHandler<EventArgs> Unloaded;
public ViewLifecycleEffect() : base($"{EffectGroupName}.{EffectName}") { }
public void RaiseLoaded(Element element) => Loaded?.Invoke(element, EventArgs.Empty);
public void RaiseUnloaded(Element element) => Unloaded?.Invoke(element, EventArgs.Empty);
}
2.In Mainpage.xmal:
<StackLayout x:Name="MainContainer" Margin="20" VerticalOptions="Center" HorizontalOptions="Center">
<Button Text="CLICK TO REMOVE" Clicked="Button_OnClicked" HorizontalOptions="Center" VerticalOptions="Center">
<Button.Effects>
<effects:ViewLifecycleEffect Loaded="ViewLifecycleEffect_OnLoaded" Unloaded="ViewLifecycleEffect_OnUnloaded"/>
</Button.Effects>
</Button>
</StackLayout>
Code-behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_OnClicked(object sender, EventArgs e)
{
MainContainer.Children.Clear();
}
private void ViewLifecycleEffect_OnLoaded(object sender, EventArgs e)
{
DisplayAlert("LOADED", "Button was added", "OK");
}
private void ViewLifecycleEffect_OnUnloaded(object sender, EventArgs e)
{
DisplayAlert("UNLOADED", "Button was removed", "OK");
}
}
3.Create a IosLifecycleEffect in the iOS project.
[assembly:ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly:ExportEffect(typeof(IosLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.iOS.Effects
{
public class IosLifecycleEffect : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.OldNew | NSKeyValueObservingOptions.Prior;
private ViewLifecycleEffect _viewLifecycleEffect;
private IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
UIView nativeView = Control ?? Container;
_isLoadedObserverDisposable = nativeView?.AddObserver("superview", ObservingOptions, IsViewLoadedObserver);
}
protected override void OnDetached()
{
_viewLifecycleEffect.RaiseUnloaded(Element);
_isLoadedObserverDisposable.Dispose();
}
private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
{
if (!nsObservedChange.NewValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseLoaded(Element);
else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseUnloaded(Element);
}
}
}

Async Programming with ListViewItem

I'm new in C# programming.
I want to write a simple Form application where on a ListView after each action, I will be updated what is going on.
I wrote some code which should do the job.
The application is working good but the update on the ListView not.
namespace ReportingTool
{
public partial class Form1 : Form
{
public static Form1 form;
public Form1()
{
InitializeComponent();
form = this;
WriteToList("1", "Program started");
}
private async void button1_Click(object sender, EventArgs e)
{
WriteToList("2", "Opening Chrome Browser");
PageNavigation driver = new PageNavigation();
await driver.BrowserActions();
WriteToList("3", "Opening TalentLink page");
await driver.GoToTalentLinkPageAsync();
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
}
public void WriteToList(string id, string action)
{
form.Activate();
ListViewItem x = new ListViewItem(id);
x.SubItems.Add(action);
form.listView1.Items.Add(x);
form.listView1.Refresh();
}
}
}
Thank you for your help.
Unfortunately there is a bug (or lack of implementation) on ListView. Basically if you make modification to any item on your existing list, this update will not be visible on UI despite being updated in code. Somehow you have to force the refresh on ItemSource in ListView. The only way I've managed to do it is save existing List of items set ItemSource to null and add updated list to the ItemSource, so:
public void WriteToList(string id, string action)
{
form.Activate();
ListViewItem x = new ListViewItem(id);
x.SubItems.Add(action);
form.listView1.Items.Add(x);
form.listView1.Refresh();
List<YourItems> newList = new List<YourItems>();
newList = form.listView1;
form.listView.ItemSource = null;
form.listView.ItemSource = newList;
}
Also I don't think that setting Static field of your class is a good way of approaching it.

How to change the toolbar back icon in xamarin forms android

I am working on xamarin forms. I wanted to change the Toolbar back icon how to do it. I searched lot about it. I didn't get proper solution. Any help would be appreciated.
Thanks
praveen
Try this
LoadApplication(new App());
var upArrow = Resources.GetDrawable(Resource.Drawable.abc_ic_ab_back_mtrl_am_alpha);
upArrow.SetColorFilter(Resources.GetColor(Resource.Color.white), PorterDuff.Mode.SrcIn);
ActionBar.SetHomeAsUpIndicator(upArrow);
References
https://forums.xamarin.com/discussion/57791/cant-change-android-back-button-in-xamarin-forms
https://forums.xamarin.com/discussion/103317/change-navigation-bar-back-button-color-in-xamarin-android
How to change the toolbar back icon in xamarin forms android
You could refer to my answer: How to change navigation page back button in xamarin forms.
I write it here again:
We need to custom a NavigationPageRenderer, override the OnPushAsync method to set the Toolbar's navigation icon.
using AToolbar = Android.Support.V7.Widget.Toolbar;
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(NavigationPageRendererDroid))] // APPCOMP
...
public class NavigationPageRendererDroid : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer // APPCOMP
{
public AToolbar toolbar;
public Activity context;
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, animated);
context = (Activity)Xamarin.Forms.Forms.Context;
toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
if (toolbar != null)
{
if (toolbar.NavigationIcon != null)
{
toolbar.NavigationIcon = Android.Support.V4.Content.ContextCompat.GetDrawable(context, Resource.Drawable.Back);
//toolbar.SetNavigationIcon(Resource.Drawable.Back);
}
}
return retVal;
}
}
The CustomNavigationPage are defined in PCL :
public class CustomNavigationPage : NavigationPage
{
public CustomNavigationPage(Page startupPage) : base(startupPage)
{
}
}
Usage :
public App()
{
InitializeComponent();
MainPage = new CustomNavigationPage(new MainPage());
}
...
// In MainPage
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new TestPage());
}
[Effect].

Communicate between pages in wpf

I have two Pages and one MainWindow.. I load the Pages in two Frames.. Now I want to execute methods from each other.. How can I do this?
This is Page1.cs:
public partial class Page1 : Page
{
public Method1()
{
doSomething;
}
}
This is Page2.cs:
public partial class Page2 : Page
{
public Method2()
{
doSomethingElse;
}
}
In my MainWindow the following happens:
Frame1.Source = new Uri("/Source/Pages/Page1.xaml", UriKind.RelativeOrAbsolute);
Frame2.Source = new Uri("/Source/Pages/Page2.xaml", UriKind.RelativeOrAbsolute);
Is there any way, to execute Method2 from Page1.cs, and Method1 from Page2.cs?
Regards
One way to do this is through their common parent, the window.
Looking at this (modified accordingly)
public partial class MainWindow : Window
{
public Page1 Page1Ref = null;
public Page1 Page2Ref = null;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Frame1.Source = new Uri("/Source/Pages/Page1.xaml", UriKind.Relative);
Frame1.ContentRendered += Frame1_ContentRendered;
// do the same for the Frame2
}
private void Frame1_ContentRendered(object sender, EventArgs e)
{
var b = Frame1.Content as Page1; // Is now Home.xaml
Page1Ref = b;
if(Page2Ref != null) // because you don't know which of the pages gets rendered first
{
Page2Ref.Page1Ref = Page1Ref; // add the Page1Ref prop to your Page2 class
Page1Ref.Page2Ref = Page2Ref; // here the same
}
}
// do the same for the other page
}
from this question
you should be able to set a reference once a page is loaded to the other page .
Better yet, you might want to let the Pages know of their window parent and access the other page through it. Either way, is bad design, I'm telling you.
Is not a solution to be proud of, you might better look into MVVM, and go with it.
Let me know if it worked for you.

navigation to a new page removes backstack

I have a strange recurring problem. Sometimes it goes away, other times it comes back. I can't pinpoint at all the issue, all my breakpoints seem to be hit in expected order.
When I navigate to a new page, my backstack keeps getting deleted, so pressing back just backgrounds the app. Obviously this is a problem.
I think it may be a result of my more complex page and viewmodel structures. I created a new class for all the NavigationHelper stuff for Pages enforcing that all my Pages subclass from the new class. I enforce that all my Pages attach themselves to a base PageViewModel class to resolve the communication between the two (I had a better way but Xaml doesn't play well), and I navigate using a NavigationService, where I call CurrentFrame, which is a static method for return Windows.Current.Content as Frame.
Here are what I think are relevant code. Any ideas? Thanks a bunch in advance. I have no clue what's going on :/
I navigate forward using the Navigate method in NavigationService (not the other two lolol), but my back button doesn't go back properly.
public abstract class BaseViewModelPage : Page
{
protected readonly NavigationHelper NavigationHelper;
protected BaseViewModelPage()
{
NavigationHelper = new NavigationHelper(this);
NavigationHelper.LoadState += navigationHelper_LoadState;
NavigationHelper.SaveState += navigationHelper_SaveState;
this.NavigationCacheMode = NavigationCacheMode.Required;
}
protected BasePageViewModel CurrentPageViewModel
{
get { return DataContext as BasePageViewModel; }
}
#region Navigation Registration
protected override void OnNavigatedTo(NavigationEventArgs e)
{
NavigationHelper.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
NavigationHelper.OnNavigatedFrom(e);
}
protected virtual void LoadState(LoadStateEventArgs e)
{
if (CurrentPageViewModel != null)
{
CurrentPageViewModel.LoadState(e);
}
}
protected virtual void SaveState(SaveStateEventArgs e)
{
if (CurrentPageViewModel != null)
{
CurrentPageViewModel.SaveState(e);
}
}
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
LoadState(e);
}
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
SaveState(e);
}
#endregion
}
public abstract class BasePageViewModel : ViewModelBase
{
private bool _isLoading = false;
public bool IsLoading
{
get
{
return _isLoading;
}
set
{
if (_isLoading == value)
{
return;
}
_isLoading = value;
RaisePropertyChanged();
}
}
public virtual void LoadState(LoadStateEventArgs e)
{
}
public virtual void SaveState(SaveStateEventArgs e)
{
}
}
public class NavigationService : INavigationService
{
public static readonly Dictionary<Type, Type> PageDictionary;
static NavigationService()
{
PageDictionary = new Dictionary<Type, Type>();
PageDictionary.Add(typeof(LogInPageViewModel), typeof(LogInPage));
PageDictionary.Add(typeof(RegisterUserPageViewModel), typeof(RegisterUserPage));
}
public bool Navigate(Type pageViewModelType, Object parameter = null)
{
if (PageDictionary.ContainsKey(pageViewModelType))
{
if (parameter != null)
{
return App.CurrentFrame.Navigate(PageDictionary[pageViewModelType], parameter);
}
else
{
return App.CurrentFrame.Navigate(PageDictionary[pageViewModelType]);
}
}
return false;
}
public bool GoBack()
{
if (CanGoBack())
{
App.CurrentFrame.GoBack();
}
return false;
}
public bool CanGoBack()
{
return App.CurrentFrame.CanGoBack;
}
public bool NavigateAndRemoveSelf(Type pageViewModelType, object parameter = null)
{
if (Navigate(pageViewModelType, parameter))
{
if (App.CurrentFrame.CanGoBack)
{
App.CurrentFrame.BackStack.RemoveAt(App.CurrentFrame.BackStackDepth - 1);
return true;
}
}
return false;
}
public bool NavigateAndRemoveAll(Type pageViewModelType, object parameter = null)
{
if (Navigate(pageViewModelType, parameter))
{
while (App.CurrentFrame.CanGoBack)
{
App.CurrentFrame.BackStack.RemoveAt(App.CurrentFrame.BackStackDepth - 1);
}
return true;
}
return false;
}
}
Update [solved]:
The error is caused by using a Universal App Class Library.
I wanted to separate the NavigationHelper.cs class (generated by default in WP8 apps) into a library. so that I could unit test the VM directly (I could not reference the WP8 app with the Unit Test project). Thus, I placed the NavigationHelper.cs class, plus all my relevant code above, in a new Universal App Class Library.
The NavigationHelper class relies on two things, a WINDOWS_PHONE_APP macro in the BUILD, which affects this specific part in the NavigationHelper class, the HardwareButton BackPressed listener.
#if WINDOWS_PHONE_APP
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
#else
and a second reliance on the Windows.Phone assembly. The assembly exists in a WP8 app, but not for a Universal App Class Library. This means that even if I add the WINDOWS_PHONE_APP macro to the library, the app will not compile. You cannot use the NavigationHelper generated by Windows Phone 8/8.1 projects inside a Universal App Class Library. I will try to raise this issue. Thanks!
Update [solved]:
The error is caused by using a Universal App Class Library.
I wanted to separate the NavigationHelper.cs class (generated by default in WP8 apps) into a library. so that I could unit test the VM directly (I could not reference the WP8 app with the Unit Test project). Thus, I placed the NavigationHelper.cs class, plus all my relevant code above, in a new Universal App Class Library.
The NavigationHelper class relies on two things, a WINDOWS_PHONE_APP macro in the BUILD, which affects this specific part in the NavigationHelper class, the HardwareButton BackPressed listener.
#if WINDOWS_PHONE_APP
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
#else
...
#endif
Because the MACRO wasn't defined, the back button wouldn't actually go back.
A second problem was the missing Windows.Phone assembly. The assembly exists in a WP8 app, but not for a Universal App Class Library. This means that even if I add a WINDOWS_PHONE_APP macro to the library, the app will not compile. You cannot use the NavigationHelper generated by Windows Phone 8/8.1 projects inside a Universal App Class Library. I will try to raise this issue. Thanks!
You can leave your NavigationHelper in your shared project, just add this to your MainPage in the Windows Phone project..
static MainPage()
{
HardwareButtons.BackPressed += (sender, args) =>
{
var frame = Window.Current.Content as Frame;
if (frame != null && frame.CanGoBack)
{
frame.GoBack();
args.Handled = true;
}
};
}
This solved my BackButton issues.

Categories

Resources