I'm using Windows Template Studio (Prism) to create a test project, but from the documentation, I'm unable to figure out navigation. I now know how to do it with MVVM Light, but had to ask, because documentation doesn't detail it. How do I navigate from page to page in Prism Windows Template Studio?
Works for MVVM Light:
ViewModelLocator.Current.NavigationService.Navigate(typeof(MyViewModel).FullName, null)
Works on Template10:
BootStrapper.Current.NavigationService.Navigate(typeof(MyViewPage), null)
You could create test app with Windows Template Studio and check Navigation Page project type then check prism Design pattern. You will find the _navigationService in the ShellViewModel class.
Works on Windows Template Studio Prism
_navigationService.Navigate(pageKey, null);
ShellViewModel.cs
public class ShellViewModel : ViewModelBase
{
private static INavigationService _navigationService;
private WinUI.NavigationView _navigationView;
private bool _isBackEnabled;
private WinUI.NavigationViewItem _selected;
public ICommand ItemInvokedCommand { get; }
public bool IsBackEnabled
{
get { return _isBackEnabled; }
set { SetProperty(ref _isBackEnabled, value); }
}
public WinUI.NavigationViewItem Selected
{
get { return _selected; }
set { SetProperty(ref _selected, value); }
}
public ShellViewModel(INavigationService navigationServiceInstance)
{
_navigationService = navigationServiceInstance;
ItemInvokedCommand = new DelegateCommand<WinUI.NavigationViewItemInvokedEventArgs>(OnItemInvoked);
}
public void Initialize(Frame frame, WinUI.NavigationView navigationView)
{
_navigationView = navigationView;
frame.NavigationFailed += (sender, e) =>
{
throw e.Exception;
};
frame.Navigated += Frame_Navigated;
_navigationView.BackRequested += OnBackRequested;
}
private void OnItemInvoked(WinUI.NavigationViewItemInvokedEventArgs args)
{
var item = _navigationView.MenuItems
.OfType<WinUI.NavigationViewItem>()
.First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageKey = item.GetValue(NavHelper.NavigateToProperty) as string;
_navigationService.Navigate(pageKey, null);
}
private void Frame_Navigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = _navigationService.CanGoBack();
Selected = _navigationView.MenuItems
.OfType<WinUI.NavigationViewItem>()
.FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private void OnBackRequested(WinUI.NavigationView sender, WinUI.NavigationViewBackRequestedEventArgs args)
{
_navigationService.GoBack();
}
private bool IsMenuItemForPageType(WinUI.NavigationViewItem menuItem, Type sourcePageType)
{
var sourcePageKey = sourcePageType.Name;
sourcePageKey = sourcePageKey.Substring(0, sourcePageKey.Length - 4);
var pageKey = menuItem.GetValue(NavHelper.NavigateToProperty) as string;
return pageKey == sourcePageKey;
}
}
_navigationService comes from ShellViewModel construct method. And this instance was created in the App class.
protected override UIElement CreateShell(Frame rootFrame)
{
var shell = Container.Resolve<ShellPage>();
shell.SetRootFrame(rootFrame);
return shell;
}
Related
WPF GeckoBrowser Not able to load url in the windows browser
I am using gecko web browser for my WPF windows application. We have a particular URL which is opening in all the system browser but not working on the windows browser.But we use any other it loads. When contacted the support team they were saying need to enable javascript. So please help me how to enable the javascript in gecko browser.
The URL load is the partial load. we are getting the background related UI for the URL. The body part of the url is not loading
I have used inbuilt .net browser but that URL is not loading in that application also.
If you want to use WebView2 in your project you can do the following:
Add nuget package Microsoft.Web.WebView2
Create WPF view with WebView2:
<wpf:WebView2 x:Name="WebView2">
<i:Interaction.Behaviors>
<behaviors:WebView2NavigateBehavior Url="{Binding Url}" RefreshInterval="{Binding RefreshInterval}" />
</i:Interaction.Behaviors>
</wpf:WebView2>
With code behind:
public partial class BrowserView : IDisposable
{
private bool disposed;
static BrowserView()
{
string loaderPath = ServiceLocator.Current.Resolve<IPathResolver>().GetWebView2LoaderDllDirectory(RuntimeInformation.ProcessArchitecture);
CoreWebView2Environment.SetLoaderDllFolderPath(loaderPath);
}
public BrowserView()
{
this.InitializeComponent();
this.InitializeAsync();
}
private async void InitializeAsync()
{
try
{
await this.WebView2.EnsureCoreWebView2Async();
}
catch (Exception ex)
{
//Log exception here
}
}
public void Dispose()
{
if (!this.disposed)
{
this.WebView2?.Dispose();
this.disposed = true;
}
}
}
Here's the code for view behavior:
public sealed class WebView2NavigateBehavior : BehaviorBase<WebView2>
{
public static readonly DependencyProperty UrlProperty =
DependencyProperty.Register(nameof(Url), typeof(WebsiteUrl), typeof(WebView2NavigateBehavior),
new PropertyMetadata(default(WebsiteUrl), PropertyChangedCallback));
public static readonly DependencyProperty RefreshIntervalProperty =
DependencyProperty.Register(nameof(RefreshInterval), typeof(TimeSpan), typeof(WebView2NavigateBehavior),
new PropertyMetadata(default(TimeSpan), PropertyChangedCallback));
private DispatcherTimer? timer;
public WebsiteUrl? Url
{
get => (WebsiteUrl?)this.GetValue(UrlProperty);
set => this.SetValue(UrlProperty, value);
}
public TimeSpan RefreshInterval
{
get => (TimeSpan)this.GetValue(RefreshIntervalProperty);
set => this.SetValue(RefreshIntervalProperty, value);
}
protected override void OnSetup()
{
base.OnSetup();
this.AssociatedObject.CoreWebView2InitializationCompleted += this.OnCoreWebView2InitializationCompleted;
}
protected override void OnCleanup()
{
base.OnCleanup();
this.StopRefresh();
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (WebView2NavigateBehavior)d;
if (e.Property == UrlProperty && e.NewValue is WebsiteUrl url)
behavior.Navigate(url);
else if (e.Property == RefreshIntervalProperty && e.NewValue is TimeSpan interval)
{
behavior.StopRefresh();
if (interval != TimeSpan.Zero)
behavior.StartRefresh(interval);
}
}
private void Navigate(WebsiteUrl? url)
{
if (this.AssociatedObject.IsInitialized && this.AssociatedObject.CoreWebView2 != null && url != null)
this.AssociatedObject.CoreWebView2.Navigate(url.ToString());
}
private void OnCoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
this.AssociatedObject.CoreWebView2InitializationCompleted -= this.OnCoreWebView2InitializationCompleted;
if (e.IsSuccess)
this.Navigate(this.Url);
}
private void StartRefresh(TimeSpan interval)
{
this.timer = new DispatcherTimer { Interval = interval };
this.timer.Tick += this.OnTick;
this.timer.Start();
}
private void StopRefresh()
{
if (this.timer != null)
{
this.timer.Stop();
this.timer.Tick -= this.OnTick;
}
this.timer = null;
}
private void OnTick(object sender, EventArgs e)
{
if (this.AssociatedObject.IsInitialized)
this.AssociatedObject.CoreWebView2?.Reload();
}
}
The code for the ViewModel:
public class BrowserViewModel : ViewModelBase<BrowserViewModel>
{
private WebsiteUrl? url;
private string? title;
private TimeSpan refreshInterval;
public WebsiteUrl? Url
{
get => this.url;
set => this.SetProperty(ref this.url, value);
}
public string? Title
{
get => this.title;
set => this.SetProperty(ref this.title, value);
}
public TimeSpan RefreshInterval
{
get => this.refreshInterval;
set => this.SetProperty(ref this.refreshInterval, value);
}
}
We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
However, we'd also like to reuse this behaviors when a submit button is tapped.
So I need to fire the TextChanged event manually, any ideas how I can do this, now sure if it's possible ?
public class NotEmptyEntryBehaviour : Behavior<Entry>
{
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(bindable);
}
void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (args == null)
return;
var oldString = args.OldTextValue;
var newString = args.NewTextValue;
}
}
If you want an alternative you can use one of the pre-built validation behaviors that comes with Xamarin.CommunityToolkit package, like TextValidationBehavior (by specifying a Regexp) or any more specific derived ones (example NumericValidationBehavior) that may fit your needs or even create a custom one by sub-classing ValidationBehavior.
It let you define custom styles for Valid and InValid states, but more important for the question has an async method called ForceValidate().
Also the Flags property could be interesting.
NotEmptyEntryBehaviour seems closer to TextValidationBehavior with MinimumLenght=1
xaml
<Entry Placeholder="Type something..." x:Name="entry">
<Entry.Behaviors>
<xct:TextValidationBehavior Flags="ValidateOnValueChanging"
InvalidStyle="{StaticResource InvalidEntryStyle}"
ValidStyle="{StaticResource ValidEntryStyle}"/>
</Entry.Behaviors>
</Entry>
Code
await (entry.Behaviors[0] as TextValidationBehavior)?.ForceValidate();
Docs
https://learn.microsoft.com/en-us/xamarin/community-toolkit/behaviors/charactersvalidationbehavior
Repo Samples
https://github.com/xamarin/XamarinCommunityToolkit/tree/main/samples/XCT.Sample/Pages/Behaviors
EDIT
If you want to run the validation from the ViewModel you need to bind ForceValidateCommand as explained in this GitHub discussion/question.
We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
You can create custom Entry with behavior to get.
The first I’m going to do is to create a new control that inherits from Entry and will add three properties: IsBorderErrorVisible, BorderErrorColor, ErrorText.
public class ExtendedEntry : Entry
{
public static readonly BindableProperty IsBorderErrorVisibleProperty =
BindableProperty.Create(nameof(IsBorderErrorVisible), typeof(bool), typeof(ExtendedEntry), false, BindingMode.TwoWay);
public bool IsBorderErrorVisible
{
get { return (bool)GetValue(IsBorderErrorVisibleProperty); }
set
{
SetValue(IsBorderErrorVisibleProperty, value);
}
}
public static readonly BindableProperty BorderErrorColorProperty =
BindableProperty.Create(nameof(BorderErrorColor), typeof(Xamarin.Forms.Color), typeof(ExtendedEntry), Xamarin.Forms.Color.Transparent, BindingMode.TwoWay);
public Xamarin.Forms.Color BorderErrorColor
{
get { return (Xamarin.Forms.Color)GetValue(BorderErrorColorProperty); }
set
{
SetValue(BorderErrorColorProperty, value);
}
}
public static readonly BindableProperty ErrorTextProperty =
BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(ExtendedEntry), string.Empty);
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set
{
SetValue(ErrorTextProperty, value);
}
}
}
Then creating custom render to android platform.
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
namespace FormsSample.Droid
{
public class ExtendedEntryRenderer : EntryRenderer
{
public ExtendedEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null || e.NewElement == null) return;
UpdateBorders();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null) return;
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName)
UpdateBorders();
}
void UpdateBorders()
{
GradientDrawable shape = new GradientDrawable();
shape.SetShape(ShapeType.Rectangle);
shape.SetCornerRadius(0);
if (((ExtendedEntry)this.Element).IsBorderErrorVisible)
{
shape.SetStroke(3, ((ExtendedEntry)this.Element).BorderErrorColor.ToAndroid());
}
else
{
shape.SetStroke(3, Android.Graphics.Color.LightGray);
this.Control.SetBackground(shape);
}
this.Control.SetBackground(shape);
}
}
}
Finally, Creating an Entry Behavior, handle the error to provide ui feedback to the user when validation occurs
public class EmptyEntryValidatorBehavior : Behavior<ExtendedEntry>
{
ExtendedEntry control;
string _placeHolder;
Xamarin.Forms.Color _placeHolderColor;
protected override void OnAttachedTo(ExtendedEntry bindable)
{
bindable.TextChanged += HandleTextChanged;
bindable.PropertyChanged += OnPropertyChanged;
control = bindable;
_placeHolder = bindable.Placeholder;
_placeHolderColor = bindable.PlaceholderColor;
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
ExtendedEntry customentry = (ExtendedEntry)sender;
if (!string.IsNullOrEmpty(customentry.Text))
{
((ExtendedEntry)sender).IsBorderErrorVisible = false;
}
else
{
((ExtendedEntry)sender).IsBorderErrorVisible = true;
}
}
protected override void OnDetachingFrom(ExtendedEntry bindable)
{
bindable.TextChanged -= HandleTextChanged;
bindable.PropertyChanged -= OnPropertyChanged;
}
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName && control != null)
{
if (control.IsBorderErrorVisible)
{
control.Placeholder = control.ErrorText;
control.PlaceholderColor = control.BorderErrorColor;
control.Text = string.Empty;
}
else
{
control.Placeholder = _placeHolder;
control.PlaceholderColor = _placeHolderColor;
}
}
}
}
Update:
You can change custom entry's IsBorderErrorVisible in button.click, to call this from submit button.
private void btn1_Clicked(object sender, EventArgs e)
{
if(string.IsNullOrEmpty(entry1.Text))
{
entry1.IsBorderErrorVisible = true;
}
}
<customentry:ExtendedEntry
x:Name="entry1"
BorderErrorColor="Red"
ErrorText="please enter name!">
<customentry:ExtendedEntry.Behaviors>
<behaviors:EmptyEntryValidatorBehavior />
</customentry:ExtendedEntry.Behaviors>
</customentry:ExtendedEntry>
I am tying to further understand MVVM with some example scenario. I have a rootpage with a 'maindisplay' textblock. I would like to display 'status' or 'scenarios' from activation of any form of UI eg. togglebutton on the 'maindisplay' textblock.
I am able to bind the the page navigation info in the rootpageviewmodel to the textblock. However, I am not able to achieve the result when displaying info from different page.
I have checked another post multiple-viewmodels-in-same-view & Accessing a property in one ViewModel from another it's quite similar but it didn't work.
Please help. Thanks.
While accessing the RootPageViewModel should retain the instance?
View
<TextBlock Text="{x:Bind RootViewModel.MainStatusContent, Mode=OneWay}"/>
RootPage.xaml.cs
public sealed partial class RootPage : Page
{
private static RootPage instance;
public RootPageViewModel RootViewModel { get; set; }
public RootPage()
{
RootViewModel = new RootPageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
public static RootPage Instance
{
get
{
if (instance == null)
{
instance = new RootPage();
}
return instance;
}
}
private void nvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
RootViewModel.MainStatusContent = "Settings_Page";
}
else
{
var navItemTag = args.InvokedItemContainer.Tag.ToString();
RootViewModel.MainStatusContent = navItemTag;
switch (navItemTag)
{
case "Home_Page":
contentFrame.Navigate(typeof(HomePage));
break;
case "Message_Page":
contentFrame.Navigate(typeof(MessagePage));
break;
}
}
}
}
RootPage ViewModel:
public class RootPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static RootPageViewModel instance = new RootPageViewModel();
public static RootPageViewModel Instance
{
get
{
if (instance == null)
instance = new RootPageViewModel();
return instance;
}
}
public RootPageViewModel()
{
}
private string _mainStatusContent;
public string MainStatusContent
{
get
{
return _mainStatusContent;
}
set
{
_mainStatusContent = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MessagePage.xaml.cs - to access RootPage ViewModel
public sealed partial class MessagePage : Page
{
public MessagePageViewModel MessageViewModel { get; set; }
public MessagePage()
{
MessageViewModel = new MessagePageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 De-Selected";
}
}
When I debug the value did write to the instance but did't update the TextBlock. Did I do anything wrong in my XAML binding?
UWP C# MVVM How To Access ViewModel from Other Page
The better way is make static variable for RootPage, but not make singleton instance for RootPage and RootPageViewModel.
For example:
public RootPage ()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Instance = this;
RootViewModel = new RootPageViewModel();
}
public static RootPage Instance;
Usage
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 De-Selected";
}
Im trying to access my bindable property for a custom button that I attempt to code a renderer for. First here is my PCL renderer:
public class BtnRenderer : Button
{
public static readonly BindableProperty HighLightProperty = BindableProperty.Create(nameof(HighlightedBackgroundColor), typeof(Color), typeof(BtnRenderer), default(Color));
public Color HighlightedBackgroundColor
{
get
{
return (Color)GetValue(HighLightProperty);
}
set
{
SetValue(HighLightProperty, value);
}
}
}
As you can see, I intend to set a HighlightedBackgroundColor from XAML, However, I don't know how to access it in my iOS renderer, what I have is:
[assembly: ExportRenderer(typeof(BtnRenderer), typeof(BtnRendereriOS))]
namespace BluetoothExample.iOS
{
public class BtnRendereriOS : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var normalBackgroundColor = Element.BackgroundColor.ToUIColor();
var _highlightBackgroundColor = Element.HighlightedBackgroundColor.ToUIColor(); //HERE IS MY PROBLEM
async Task NormalColorState(UIButton button)
{
await UIView.TransitionNotifyAsync(button, .25, UIViewAnimationOptions.TransitionCrossDissolve, () =>
{
button.BackgroundColor = normalBackgroundColor;
});
}
Control.TouchDown += async (object sender, EventArgs c) =>
{
var button = sender as UIButton;
await UIView.TransitionNotifyAsync(button, .25, UIViewAnimationOptions.TransitionCrossDissolve, () =>
{
button.BackgroundColor = _highlightBackgroundColor;
});
};
}
}
}
How do I access this property correctly?
//HERE IS MY PROBLEM
var _highlightBackgroundColor = Element.HighlightedBackgroundColor.ToUIColor();
Directly using Element is the base of your renderer (VisualElementRenderer<TElement>) so in order to access any custom properties on your subclass, just cast it (BtnRenderer in this case):
var _highlightBackgroundColor = (Element as BtnRenderer).HighlightedBackgroundColor.ToUIColor();
I am trying to pass strings between forms. Why does it not? Am I missing something or is it an error in the program or what?
On UserControl3
UserControl1 u1;
public UserControl3()
{
u1 = new UserControl1();
InitializeComponent();
}
On UserControl3
public void materialCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if (materialCheckBox1.Checked)
{
u1.toUserControl3 = "GOINTHEBOX!";
}
else
{
u1.toUserControl3 = string.Empty;
}
}
On UserControl1
public string toUserControl3
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
On UserControl1
public void textBox1_TextChanged(object sender, EventArgs e)
{
}
Changing the Text property on a control through a piece of code doesn't necessarily mean the value control will update. Typically you need some sort of binding between your property, in this case toUserControl3, and your control. You need a way to tell your control that value changed so it knows to update.
You could accomplish databinding in the following way:
Create a new class to handle state and binding: This eliminated any need to pass controls into constructors of other controls.
public class ViewModel : INotifyPropertyChanged
{
public string TextBoxText => CheckBoxChecked ? "GOINTOTHEBOX!" : string.Empty;
public bool CheckBoxChecked
{
get { return _checkBoxChecked; }
set
{
_checkBoxChecked = value;
OnPropertyChanged("CheckBoxChecked");
}
}
private bool _checkBoxChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is your main form
public void Form1
{
public Form1(ViewModel viewModel)
{
UserControl1.DataBindings.Add("TextBoxTextProperty", viewModel, "TextBoxText");
UserControl3.DataBindings.Add("MaterialCheckBoxCheckedProperty", viewModel, "CheckBoxChecked");
}
}
UserControl1
public void UserControl1()
{
public string TextBoxTextProperty
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
}
UserControl3
public void UserControl3()
{
public bool MaterialCheckBoxCheckedProperty
{
get { return materialCheckBox1.Checked; }
set { materialCheckBox1.Checked = value; }
}
}