How to access CommandBar controls from Frame - c#

I'm trying to dynamically access my CommandBar from frames to control its back button. How can I ensure the CommandBar back button is hidden on the first frame (Frame1) whilst being visible and clickable on the second frame (Frame2)?
MainPage.xaml
<Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CommandBar>
<CommandBar.Content>
<Button
Click="Back_Click"
x:FieldModifier="public"
Style="{StaticResource NavigationBackButtonNormalStyle}"
Name="BackButton"
VerticalAlignment="Top" />
</CommandBar.Content>
</CommandBar>
<Frame Name="MyFrame"/>
</Grid>
</Page>
MainPage.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Current = this;
Frame_Main.Navigate(typeof(Frame1));
}
public static MainPage Current;
private void Back_Click(object sender, RoutedEventArgs e)
{
On_BackRequested();
}
private bool On_BackRequested()
{
if (this.Frame.CanGoBack)
{
this.Frame.GoBack();
return true;
}
return false;
}
private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
On_BackRequested();
args.Handled = true;
}
}
Frame1.cs
public sealed partial class Frame1: Page
{
public Frame1()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Collapsed;
}
}
Frame2.cs
public sealed partial class Frame2: Page
{
public Frame2()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Visible;
MainPage.Current.BackButton.IsEnabled = true;
}
}

If you want to access the Back Button from Frame1 and Frame2, you could try to set the x:FieldModifier of BackButton as public. In this case, it will be public and you can access the button by its x:name from other pages. In addition, it's better to put the click event in the MainPage instead of Frame2. For example:
MainPage.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CommandBar>
<CommandBar.Content>
<Button x:FieldModifier="public" Click="Back_Click" Name="BackButton" Style="{StaticResource NavigationBackButtonNormalStyle}" VerticalAlignment="Top"/>
</CommandBar.Content>
</CommandBar>
<Frame Name="MyFrame" Grid.Row="1"/>
</Grid>
MainPage.xaml.cs:
You need to define a public static MainPage instance to let other pages access your button through this instance.
public MainPage()
{
this.InitializeComponent();
Current = this;
MyFrame.Navigate(typeof(Frame1));
}
public static MainPage Current;
private void Back_Click(object sender, RoutedEventArgs e)
{
On_BackRequested();
}
private bool On_BackRequested()
{
if (MyFrame.CanGoBack)
{
MyFrame.GoBack();
return true;
}
return false;
}
private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
On_BackRequested();
args.Handled = true;
}
Frame1.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Collapsed;
}
Frame2.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Visible;
MainPage.Current.BackButton.IsEnabled = true;
}
Or you can define a property to bind with the Visibility of Button, when you navigate to next page, you can pass the property in the Navigate method(e.g. Frame_Main.Navigate(typeof(Frame1), VM);) and then in the OnNavigatedTo event to change its value.

Related

C# WinUI 3 frame navigation stack

In my WinUI3 C# application I have a frame with a content page. Given that I set IsNavigationStackEnabled to true on the frame, I'd expect the current page to be pushed to the navigation stack when I navigate (using MyFrame.Navigate(...)) so I could use MyFrame.GoBack() to navigate back to the previous page.
However, trying to invoke MyFrame.GoBack() after navigating from one page to another always results in an exception: System.Runtime.InteropServices.COMException: 'Error HRESULT E_FAIL has been returned from a call to a COM component.'. What's more, the CanGoBack-property is always false.
Am I missing something or is the navigation stack just not managed by the frame in WinUI3 and do I need to do this manually?
This a working sample code for Frame navigation. Note that there's no IsNavigationStackEnabled in the code. IsNavigationStackEnabled is true by default.
MainWindow.xaml
<Grid ColumnDefinitions="*,*">
<StackPanel
Grid.Column="0"
Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button
x:Name="Page1Button"
Click="Page1Button_Click"
Content="Page1" />
<Button
x:Name="Page2Button"
Click="Page2Button_Click"
Content="Page2" />
<Button
x:Name="Page3Button"
Click="Page3Button_Click"
Content="Page3" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button
x:Name="BackButton"
Click="BackButton_Click"
Content="Back" />
<Button
x:Name="NextButton"
Click="NextButton_Click"
Content="Next" />
</StackPanel>
<Frame x:Name="NavigationFrame" />
</StackPanel>
<ListView Grid.Column="1" ItemsSource="{x:Bind NavigationLogs}" />
</Grid>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using System;
using System.Collections.ObjectModel;
namespace Frames;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
NavigateTo(typeof(Page1));
}
private ObservableCollection<string> NavigationLogs { get; } = new();
private void BackButton_Click(object sender, RoutedEventArgs e)
{
NavigateBack();
}
private void NextButton_Click(object sender, RoutedEventArgs e)
{
NavigateForward();
}
private void Page1Button_Click(object sender, RoutedEventArgs e)
{
NavigateTo(typeof(Page1));
}
private void Page2Button_Click(object sender, RoutedEventArgs e)
{
NavigateTo(typeof(Page2));
}
private void Page3Button_Click(object sender, RoutedEventArgs e)
{
NavigateTo(typeof(Page3));
}
private void NavigateTo(Type pageType)
{
this.NavigationFrame.Navigate(pageType);
NavigationLogs.Add($"Navigated to {pageType}.");
}
private void NavigateBack()
{
if (this.NavigationFrame.CanGoBack is true)
{
this.NavigationFrame.GoBack();
NavigationLogs.Add("Navigated back.");
}
else
{
NavigationLogs.Add("Cannot to navigate back.");
}
}
private void NavigateForward()
{
if (this.NavigationFrame.CanGoForward is true)
{
this.NavigationFrame.GoForward();
NavigationLogs.Add("Navigated forward.");
}
else
{
NavigationLogs.Add("Cannot to navigate forward.");
}
}
}

View a pdf with Webview Xamarin Android

I would like to use a webview to view a pdf but something must be missing.
Custom Webview code:
class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri), typeof(string), typeof(CustomWebView), default(string));
public string Uri {
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
CustomWebViewRenderer code:
class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var customWebView = Element as CustomWebView;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.LoadUrl(string.Format("file:///android_asset/pdfjs/web/viewer.html?file={0}", string.Format("file:///android_asset/Content/{0}", WebUtility.UrlEncode(customWebView.Uri))));
}
}
}
xaml code:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button HeightRequest="100"
WidthRequest="200"
HorizontalOptions="Center"
VerticalOptions="Center"
Clicked="Button_Clicked" Grid.Row="0"/>
<!--<local:CustomWebView Grid.Row="1" Uri="test.pdf" x:Name="PdfViewer" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>-->
<local:CustomWebView Grid.Row="1" x:Name="PdfViewer" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
mainpage.xaml.cs:
private void Button_Clicked(object sender, EventArgs e)
{
PdfViewer.Uri = "test.pdf";
}
If I set uri from xaml the pdf is displayed but if I set it from the code behind it doesn't work.
How can I set the pdf from the code behind? Using debugging when I set the pdf from the code behind does not occur CustomWebViewRenderer.OnElementChanged and therefore does not update, how can I do?
Thanks in advance.

How to create an Input Box in c# uwp that automatically do something?

I'm trying to create an input box (without buttons) that wait an input from the user (tipically numbers) and, when the lenght is equal to 24, it calls another function to do something.
I already create one but using a confirm button:
in mainpage.xaml.cs
public void WaitingInput()
{
string text = InputTextDialogAsync();
DoSomething(text);
}
private void InputTextDialogAsync()
{
TextBox inputTextBox = new TextBox();
inputTextBox.AcceptsReturn = false;
inputTextBox.Height = 32;
inputTextBox.Width = 300;
ContentDialog dialog = new ContentDialog();
dialog.Content = inputTextBox;
dialog.Title = "Input Reader";
dialog.IsPrimaryButtonEnabled = true;
dialog.IsSecondaryButtonEnabled = false;
dialog.PrimaryButtonText = "Ok";
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
return inputTextBox.Text;
else
return "";
}
it creates what I want but using the ok button to confirm.
public void DoSomething (string text) {
if (text.length < 24) {
WaitingInput();
return;
}
// Do Something with text input ...
}
I tryed with inputTextBox.TextChanging but, even if the input text satisfied the requirements (length == 24) when it finishes the load of the page the inputText appears again...
I'm missing something and maybe it exists a better solution to do that.
---- UPDATE:
Now I'm able to hide the contentdialog box but when you try to insert this input text "123456789012345678901234" I get an error saying that I cannot open more than one content dialog even if I put the Hide function
mainpage.xaml.cs
namespace App1
{
public sealed partial class MainPage : Page
{
ContentDialog dialogInput = new ContentDialog();
TextBox inputBox = new TextBox();
public MainPage()
{
this.InitializeComponent();
}
public void Page_Loaded(object sender, RoutedEventArgs e)
{
WaitingInput();
}
public async void WaitingInput()
{
inputBox.AcceptsReturn = false;
inputBox.Height = 32;
inputBox.Width = 300;
inputBox.TextChanging += TextChangingHandler;
dialogInput.Content = inputBox;
dialogInput.Title = "Input Reader";
dialogInput.IsPrimaryButtonEnabled = false;
dialogInput.IsSecondaryButtonEnabled = false;
dialogInput.PrimaryButtonText = "";
await dialogInput.ShowAsync();
}
private void TextChangingHandler(TextBox sender, TextBoxTextChangingEventArgs e)
{
if (sender.Text.Length < 24)
{
return;
}
dialogInput.Hide();
DoSomething(sender.Text);
}
private async void DoSomething(string inputTextUSER)
{
if (inputTextUSER == "123456789012345678901234")
{
WaitingInput();
return;
}
inputText.Text = inputTextUSER;
await Task.Delay(3000);
}
}
}
mainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Loaded="Page_Loaded"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<TextBox x:Name="inputText" Text="" HorizontalAlignment="Center" VerticalAlignment="Top" IsReadOnly="True" IsDoubleTapEnabled="False" IsHoldingEnabled="False" IsRightTapEnabled="False" IsTapEnabled="False" FontSize="24" FontWeight="Bold" RequestedTheme="Default" IsHitTestVisible="False" IsTabStop="False" IsTextPredictionEnabled="False" BorderThickness="0,0,0,2" BorderBrush="Black" Width="592" TextAlignment="Center" Foreground="Black" Background="Transparent" Height="45" Margin="0,50,0,0" />
</Grid>
I think OnTextChanging() is the way to go if I understand your requirements correctly.
Here is some code... but understand that there are likely other scenarios that should be accounted for... what about pasting data in that is longer than 24? validation on input? how to close the window if there are no buttons and user wants to back out (esc works, but is that good design)? etc. etc. I'm also not getting into MVVM or other concepts like that. There is also no exception handling in this code.
You should be able to modify to your liking.
Input Text Dialog control:
<ContentDialog
x:Class="App1.InputTextDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="TITLE">
<Grid>
<TextBox AcceptsReturn="False" Height="32" Width="300" TextChanging="OnTextChanging" />
</Grid>
</ContentDialog>
code behind:
public sealed partial class InputTextDialog : ContentDialog
{
public event EventHandler<EnteredTextArgs> OnValueEntered;
public InputTextDialog()
{
this.InitializeComponent();
}
private void OnTextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
if (sender.Text.Length == 24)
{
OnValueEntered?.Invoke(this, new EnteredTextArgs() { EnteredText = sender.Text });
sender.Text = string.Empty;
this.Hide();
}
}
}
args class:
public class EnteredTextArgs : EventArgs
{
public string EnteredText { get; set; }
}
main page:
<StackPanel>
<Button Content="Show Dialog" Click="OnShowClick" />
<TextBlock x:Name="txtblockResult" />
</StackPanel>
main page code behind:
public sealed partial class MainPage : Page
{
InputTextDialog dialog = new InputTextDialog();
public MainPage()
{
this.InitializeComponent();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
dialog.OnValueEntered += Dialog_OnValueEntered;
}
private void Dialog_OnValueEntered(object sender, EnteredTextArgs e)
{
txtblockResult.Text = e.EnteredText;
}
private async void OnShowClick(object sender, RoutedEventArgs e)
{
await dialog.ShowAsync();
}
}
****UPDATE****
OK. Based on your most recent update, I think this is what you intended... but without knowing the full requirements, this seems like it needs some work. Good luck!
public sealed partial class MainPage : Page
{
ContentDialog dialogInput = new ContentDialog();
TextBox inputBox = new TextBox();
public MainPage()
{
this.InitializeComponent();
//you only want to call this once... not each time you show the dialog
Setup();
Loaded += Page_Loaded;
}
public void Page_Loaded(object sender, RoutedEventArgs e)
{
WaitingInput();
}
/// <summary>
/// initializes the dialog and its child - the textbox
/// </summary>
private void Setup()
{
inputBox.AcceptsReturn = false;
inputBox.Height = 32;
inputBox.Width = 300;
inputBox.TextChanging += TextChangingHandler;
dialogInput.Content = inputBox;
dialogInput.Title = "Input Reader";
dialogInput.IsPrimaryButtonEnabled = false;
dialogInput.IsSecondaryButtonEnabled = false;
dialogInput.PrimaryButtonText = "";
}
private void ResetDialog()
{
inputBox.Text = string.Empty;
WaitingInput();
}
public async void WaitingInput()
{
await dialogInput.ShowAsync();
}
private async void TextChangingHandler(TextBox sender, TextBoxTextChangingEventArgs e)
{
if (sender.Text.Length < 24)
{
return;
}
dialogInput.Hide();
await DoSomething(sender.Text);
}
private async Task DoSomething(string inputTextUSER)
{
if (inputTextUSER == "123456789012345678901234")
{
//note: the dialog will not show again. May as well close the app
return;
}
//show inputted text in textblock
inputText.Text = inputTextUSER;
await Task.Delay(3000);
//after 3 seconds, show the dialog again - unclear requirement
ResetDialog();
}
}
mainpage.xaml:
<Grid>
<TextBox x:Name="inputText" Text="" HorizontalAlignment="Center" VerticalAlignment="Top" IsReadOnly="True" IsDoubleTapEnabled="False" IsHoldingEnabled="False" IsRightTapEnabled="False" IsTapEnabled="False" FontSize="24" FontWeight="Bold" RequestedTheme="Default" IsHitTestVisible="False" IsTabStop="False" IsTextPredictionEnabled="False" BorderThickness="0,0,0,2" BorderBrush="Black" Width="592" TextAlignment="Center" Foreground="White" Background="Transparent" Height="45" Margin="0,50,0,0" />
</Grid>

Gesture recognition blocks textbox in WP8.1

I'm implementing a control with gesture interactions for Windows Universal app. But I've found an issue, that if I define gesture setting for a container than parent TextBox control will not be clickable after that.
Here is a simplified layout code:
<Page x:Class="App.MainPage">
<Grid x:Name="RootGrid" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" />
<Button Grid.Row="1" Content="Click" />
</Grid>
</Page>
Here is a simplified code, which allows to reproduce the behavior:
public sealed partial class MainPage : Page
{
private GestureRecognizer _gr = new GestureRecognizer();
public FrameworkElement Container { get; set; }
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.Container = this.RootGrid;
this.Container.PointerCanceled += OnPointerCanceled;
this.Container.PointerPressed += OnPointerPressed;
this.Container.PointerMoved += OnPointerMoved;
this.Container.PointerReleased += OnPointerReleased;
_gr.CrossSlideHorizontally = true;
_gr.GestureSettings = GestureSettings.ManipulationTranslateRailsX;
}
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
_gr.CompleteGesture();
e.Handled = true;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessDownEvent(e.GetCurrentPoint(null));
this.Container.CapturePointer(e.Pointer);
e.Handled = true;
}
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessMoveEvents(e.GetIntermediatePoints(null));
e.Handled = true;
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessUpEvent(e.GetCurrentPoint(null));
e.Handled = true;
}
}
Debuggig said me that the main reason of this behavior is OnPointerPressed handler. This method is called when I click on the RootGrid and TextBox, but doesn't when I click on the button. object sender is always Windows.UI.Xaml.Controls.Grid so I cannot determine is it TextBox or not.
What is the most interesting that the same code work as expected for Windows app, but doesn't work for Windows Phone 8.1 app.
Could you give me any suggestion how to implement gesture recognition without affecting on controls inside?
I haven't found a better solution than adding PointerPressed event handler for TextBox control:
private void TextBox_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
}
It prevents calling OnPointerPressed for this.Container and allows using TextBox in a typical way. Not the best solution but it works as well for me.

How to bind boolean value to label

I am new to WPF. My code is as follows:
In my MainWindow.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Label HorizontalAlignment="Left" Margin="63,30,0,0" Grid.Row="0" VerticalAlignment="Top" Content="{Binding myVal}" Height="39" Width="71"/>
<Button Grid.Row="1" x:Name="btnSelect" Content="Select" Click="btnSelect_Click_1" Margin="396,0,10,0"/>
</Grid>
and MainWindow.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _myboolVal;
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void btnSelect_Click_1(object sender, RoutedEventArgs e)
{
if (myVal== false)
{
myVal = true;
}
else
{
myVal= true;
}
}
public bool myVal
{
get { return _myboolVal; }
set { _myboolVal= value; OnPropertyChanged("myVal"); }
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
But the value of the label is always false.
Your logic in btnSelect_Click_1 is incorrect. Update it to:
private void btnSelect_Click_1(object sender, RoutedEventArgs e)
{
myVal = !myVal;
}
take a look at this sample (MVVM, Command binding, MVVMLight)
Please set the Mode to TwoWay ,then it will works.
Content="{Binding myVal,Mode=TwoWay}"

Categories