I found and tried one example of a custom rendered DatePicker, for Android, for Xamarin Forms and does not show which button was clicked in UnFocus. At least not for me. Its from stackoverflow. Xamarin.Forms Android DatePicker/TimePicker button listener
Has the example in this article helped anyone else? I really need to know when the OK button is clicked.
This extends Nick Kovalsky's answer. I also fixed a bug in that answer, that meant the renderer was never used.
Subclass DatePicker, so that you can add a new BindableProperty and a new method. Place this in your cross-platform project.
OKCancelDatePicker.cs:
using Xamarin.Forms;
// Replace with YOUR namespace.
namespace TestBugs
{
/// <summary>
/// NOTE: Requires custom renderer on each platform.
/// </summary>
public class OKCancelDatePicker : DatePicker
{
public static readonly BindableProperty UserCancelledProperty = BindableProperty.Create(nameof(UserCancelled), typeof(bool), typeof(OKCancelDatePicker), false);
/// <summary>
/// Bind to "UserCancelled", to propagate this change elsewhere (e.g. to a VM, or to trigger some logic).
/// </summary>
public bool UserCancelled {
get => (bool)GetValue(UserCancelledProperty);
set => SetValue(UserCancelledProperty, value);
}
/// <summary>
/// Optionally add code here. Though usually you'll detect the change by binding to UserCancelled.
/// </summary>
public void OnPickerClosed()
{
if (UserCancelled) {
// User cancelled.
_ = 0; // Dummy code, to set a breakpoint on. You can remove this.
} else {
// User selected OK.
_ = 0; // Dummy code, to set a breakpoint on. You can remove this.
}
}
}
}
Create a renderer for Android. Place this in your .Android project.
OKCancelDatePickerRenderer.cs:
using Android.App;
using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
// Replace these with YOUR namespaces.
using TestBugs; // Contains OKCancelDatePicker.
using TestBugs.Droid; // Contains OKCancelDatePickerRenderer.
[assembly: ExportRenderer(typeof(OKCancelDatePicker), typeof(OKCancelDatePickerRenderer))]
// Replace this with YOUR Android namespace.
namespace TestBugs.Droid
{
/// <summary>
/// Based on Nick Kovalsky's https://stackoverflow.com/a/60786875/199364.
/// </summary>
public class OKCancelDatePickerRenderer : DatePickerRenderer
{
public OKCancelDatePickerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
{
base.OnElementChanged(e);
//Disposing
if (e.OldElement != null) {
_element = null;
}
//Creating
if (e.NewElement != null) {
_element = e.NewElement as OKCancelDatePicker;
}
}
protected OKCancelDatePicker _element;
protected override DatePickerDialog CreateDatePickerDialog(int year, int month, int day)
{
// This mimics what the original renderer did.
var dialog = new DatePickerDialog(Context, (o, e) =>
{
_element.Date = e.Date;
((IElementController)_element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
}, year, month, day);
// These use our custom actions when buttons pressed.
dialog.SetButton((int)DialogButtonType.Positive, Context.Resources.GetString(global::Android.Resource.String.Ok), OnOk);
dialog.SetButton((int)DialogButtonType.Negative, Context.Resources.GetString(global::Android.Resource.String.Cancel), OnCancel);
return dialog;
}
private void OnCancel(object sender, DialogClickEventArgs e)
{
// This is what the original renderer did when Cancel pressed.
_element.Unfocus();
// This is our custom logic.
_element.UserCancelled = true;
_element?.OnPickerClosed();
}
private void OnOk(object sender, DialogClickEventArgs e)
{
// This is what the original renderer did when OK pressed.
_element.Date = ((DatePickerDialog)sender).DatePicker.DateTime;
_element.Unfocus();
// This is our custom logic.
_element.UserCancelled = false;
_element?.OnPickerClosed();
}
}
}
Usage:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestBugs"
x:Class="TestBugs.DatePickerTestPage">
<ContentPage.Content>
<StackLayout>
<Label Text="Date Picker Test Page" HorizontalOptions="CenterAndExpand" />
<local:OKCancelDatePicker />
</StackLayout>
</ContentPage.Content>
</ContentPage>
IMPORTANT: If your app runs on other platforms you'll need to do similar on each platform. Per platform, try to find an example to modify. Nick's answer has a link to one for iOS.
TBD: I should add here an example of how to bind to that UserCancelled property, so you can connect it to logic in your page.
For now, read about Bindable Properties, and google for examples of binding to a BindableProperty of a control or view.
Related
I have two navigation pages, one root page, and a second page that can be accessed from the first.
I have no desire to prescribe a separate button for this when there is an arrow at the top.
Is it possible to somehow register a Clicked event handler for it yourself?
From what you are asking, I believe you are trying to have an event handler that can intercept navigation events.
Xamarin.Forms Shell has a Navigating event you can subscribe to that allows you to see the current navigation, where it intends to navigate to, and allows you to cancel navigation or redirect. This captures all navigation, so it isn't just the back button, but will also capture moving forward as well.
Here is an example of navigation being intercepted in a ContentPage:
public class ExampleContentPage : ContentPage
{
public ExampleContentPage()
{
Content = new StackLayout
{
Children = {
new Label { Text = "Welcome to Xamarin.Forms!" }
}
};
}
protected override void OnAppearing()
{
base.OnAppearing();
// subscribe to event listener on appearing
Shell.Current.Navigating += Current_Navigating;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
// remove event listener on disappearing
Shell.Current.Navigating -= Current_Navigating;
}
private async void Current_Navigating(object sender, ShellNavigatingEventArgs e)
{
if (sender is Shell shell)
{
// cancels navigation so we can do what we need
if (e.CanCancel)
{
e.Cancel();
}
// continue navigation to it's original target if we so choose
Shell.Current.Navigating -= Current_Navigating;
await Shell.Current.GoToAsync(e.Target, true);
}
}
}
However; if you're looking to override the functionality of the arrow button, I think you need to define the Shell's BackButtonBehavior.
This can be done either through XAML or C#. I've attached Microsoft's examples here:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});
And here is the documentation for overriding the back button behavior: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/navigation#back-button-behavior
To handle the backbutton event on android, you can override the OnOptionsItemSelected and OnBackPressed method in mainactivity, like:
public override void OnBackPressed()
{
// this is not necessary, but in Android user
// has both Nav bar back button and
// physical back button its safe
// to cover the both events
// retrieve the current xamarin forms page instance
var currentpage = (BackArrowTest)
Xamarin.Forms.Application.
Current.MainPage.Navigation.
NavigationStack.LastOrDefault();
// check if the page has subscribed to
// the custom back button event
if (currentpage?.CustomBackButtonAction != null)
{
currentpage?.CustomBackButtonAction.Invoke();
}
else
{
base.OnBackPressed();
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332)
{
// retrieve the current xamarin forms page instance
var currentpage = (BackArrowTest)
Xamarin.Forms.Application.
Current.MainPage.Navigation.
NavigationStack.LastOrDefault();
// check if the page has subscribed to
// the custom back button event
if (currentpage?.CustomBackButtonAction != null)
{
// invoke the Custom back button action
currentpage?.CustomBackButtonAction.Invoke();
// and disable the default back button action
return false;
}
// if its not subscribed then go ahead
// with the default back button action
return base.OnOptionsItemSelected(item);
}
else
{
// since its not the back button
//click, pass the event to the base
return base.OnOptionsItemSelected(item);
}
}
Then you can create a page which has EnableBackButtonOverrideProperty and your custom event like:
public class BackArrowTest : ContentPage
{
public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty =
BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(BackArrowTest),
false);
public bool EnableBackButtonOverride
{
get
{
return (bool)GetValue(EnableBackButtonOverrideProperty);
}
set
{
SetValue(EnableBackButtonOverrideProperty, value);
}
}}
At last you can set the event:
<bk:BackArrowTest xmlns:bk="clr-namespace:MyForms2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
EnableBackButtonOverride="True"
x:Class="MyForms2.MyPage1"
>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</bk:BackArrowTest>
codebehind:
public partial class MyPage1 : BackArrowTest
{
public MyPage1()
{
InitializeComponent();
this.EnableBackButtonOverride = true;
this.CustomBackButtonAction = () => { Console.WriteLine("Do something"); };
}
}
FYI:don't forget to set the Actionbarsupport in mainactivity like:
AndroidX.AppCompat.Widget.Toolbar toolbar = this.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
Good afternoon everyone. I have just begun to work with Xamarin Forms as I will need it for my job and wanted to learn best practices from the beginning so I went for Mvvm Light. My current problem is that, whatever I do, I really can't make binding work (NOTE: it worked for my first project, a simple page where I had a button which was used to navigate to another page). Now, for my current project, an EventApp I tried everything I could, looking on lots of forums and tried various settings and implementations but couldn't make the binding work again. I will show you some of my current code for a test page I've done just for testing purposes.
App.cs class:
public partial class App : Application
{
private static ViewModelLocator _locator;
public static ViewModelLocator Locator { get { return _locator ?? (_locator = new ViewModelLocator()); } }
public App()
{
InitializeComponent() //Tried both with and without this
registerNavigationService();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
public void registerNavigationService()
{
NavigationService nav = new NavigationService();
nav.Configure(PageNames.GeneralNotificationPage, typeof(GeneralNotificationPage));
nav.Configure(PageNames.CreateNotificationPage, typeof(CreateNotificationPage));
nav.Configure(PageNames.SelectSongPage,typeof(SelectSongPage));
SimpleIoc.Default.Register<INavigationService>(() => nav);
var firstPage = new NavigationPage(new SelectSongPage());
nav.Initialize(firstPage);
MainPage = firstPage;
}
}
`
App.xaml:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EventAppMvvm.App"
>
</Application>
-Note: here I tried to set a static resource for my ViewModelLocator the next way:
xlmns:vm="clr-namespace:EventAppMvvm.ViewModel;assembly=EventAppMvvm"
and then in resources
<vm:ViewModelLocator x:Key="Locator"/>
doesn't work, if I try in one of my pages to set the DataContext, it says it hasn't found any bindable property. If I set the BindingContext to that I get an NPE when I run the app as there is no object created. So I ended up using the code behind to do the BindingContext and instantiate the locator in my App.cs file so I won't receive an NPE.
ViewModelLocator:
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
////if (ViewModelBase.IsInDesignModeStatic)
////{
//// // Create design time view services and models
//// SimpleIoc.Default.Register<IDataService, DesignDataService>();
////}
////else
////{
//// // Create run time view services and models
//// SimpleIoc.Default.Register<IDataService, DataService>();
////}
SimpleIoc.Default.Register<CreateNotificationPageViewModel>();
SimpleIoc.Default.Register<SelectSongPageViewModel>();
SimpleIoc.Default.Register<GeneralNotificationPageViewModel>();
}
public CreateNotificationPageViewModel CreateNotificationPageVM
{
get
{
return ServiceLocator.Current.GetInstance<CreateNotificationPageViewModel>();
}
}
public SelectSongPageViewModel SelectSongPageVM
{
get
{
return ServiceLocator.Current.GetInstance<SelectSongPageViewModel>();
}
}
public GeneralNotificationPageViewModel GeneralNotificationPageVM
{
get
{
return ServiceLocator.Current.GetInstance<GeneralNotificationPageViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
One of my ViewModels(test one):
public class SelectSongPageViewModel : ViewModelBase
{
RelayCommand _commandTest;
Song _selectedSong;
INavigationService navigation;
string clicked = "3";
string _message = "I ve not been clicked yet";
public SelectSongPageViewModel(INavigationService navigation)
{
this.navigation = navigation;
}
RelayCommand CommandTest
{
get
{
if(_commandTest == null)
{
_commandTest= new RelayCommand(() =>
{
Message = _message;
//navigation.NavigateTo(PageNames.CreateEventPage);
});
}
return _commandTest;
}
}
public string Message
{
get
{
return clicked;
}
set
{
clicked = value;
RaisePropertyChanged("Message");
}
}
}
Actual page of that ViewModel:
Notes: if I try to use DataContext (defined as static resource previously in App.xaml), no matter if I give the correct namespace and assembly, it says that it hasn't found any binding, property there, if I use BindingContext, it works, I don't get any errors at compile time but I can't make the buttons do something as it is like it doesn't recognize my defined commands but for some reason it just binds simple data like my Message property.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EventAppMvvm.Views.SelectSongPage"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Message}"></Label>
<Button Text="Press me" Command="{Binding CommandTest}"></Button>
</StackLayout>
</ContentPage.Content>
</ContentPage>
And the code behind for this page.
namespace EventAppMvvm.Views
{
public partial class SelectSongPage : ContentPage
{
public SelectSongPage()
{
InitializeComponent();
var vm = App.Locator.SelectSongPageVM;
BindingContext = vm;
}
}
}
Whatever I do, I can't make the commands/Complex binding work again for some reason but still simple property binding works, I'm using VisualStudio for Mac if it matters.
currently I'm working on a chat client and changed my client from windows forms (as forms is shitty) to WPF. I'm not really sure which control could be used to realize a chatbox. I could take a TextBox but this will not display the complete content when it's full. I also tried to use a ListBox but when I try to add items they are not displayed. I used this code to add content to it:
internal void AddMessage(string message)
{
listBox_messages.Items.Add(message);
listBox_messages.Items.Refresh();
}
Does anybody knows which control would be the best for this purpose?
Thanks for your help!
Edit:
I implemented a TextBox for this and disabled it. But the text I'm appending by this method is not shown.
My class:
using System.ComponentModel;
using System.Windows;
namespace Chat_Client
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// MainWindow constructor
/// </summary>
public MainWindow()
{
InitializeComponent();
textBox_messages.AppendText("Test" + "\n");
textBox_messages.AppendText("Test" + "\n");
textBox_messages.AppendText("Test" + "\n");
Closing += OnWindowClosing;
}
private void OnWindowClosing(object sender, CancelEventArgs e)
{
Program.Shutdown();
}
private void button_connect_Click(object sender, RoutedEventArgs e)
{
if(Program.Connected)
{
Program.Disconnect();
}
else
{
Program.Connect();
}
}
private void button_sendMessage_Click(object sender, RoutedEventArgs e)
{
}
internal void AddMessage(string message)
{
textBox_messages.AppendText(message + "\n");
}
}
}
The test strings are shown but the text added by the method AddMessage is not. I can verify that the method is called, I just checked it with a breakpoint inside this method. Anybody has a clue how this could happen?
Update
If the Program class calls AddMessage(string) from within another thread than the UI thread, you have to use a Dispatcher in order to update the UI.
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
mainWindow.AddMessage(message);
}));
MVVM is the way to go when using WPF.
Add your messages to an ObservableCollection in your ViewModel class (ChatViewModel.cs):
public class ChatViewModel
{
public ObservableCollection<string> Messages { get; } = new ObservableCollection<string>();
internal void AddMessage(string message)
{
Messages.Add(message);
}
}
Set this ViewModel as the DataContext of your View (ChatView.xaml)
public ChatView : UserControl
{
public ChatView()
{
InitializeComponent();
DataContext = new ChatViewModel();
}
}
In the XAML-Code bind the ObservableCollection to the ItemsSource property of your ListBox:
<ListBox ItemsSource="{Binding Messages}") />
When you add a message to the Messages collection it should appear inside the ListBox. This is not a complete example, but it should guide you into the right direction.
*In response to your edit: where are you calling AddMessage() from? I tried your code and just called AddMessage("foo"); from the button click event and worked fine.
For a chat box, I would use a TextBox to write the chat messages, and wrap it with a ScrollViewer for scrolling. After using AppendText() on the TextBox to write the message, you can then call ScrollToEnd() to scroll to the bottom of the TextBox.
In the XAML:
<ScrollViewer x:Name="ScrollViewer" ScrollChanged="ScrollViewer_OnScrollChanged">
<TextBox x:Name="ChatBox"/>
</ScrollViewer>
In the code behind:
private void WriteToChat(string message)
{
ChatBox.AppendText(message);
ChatBox.ScrollToEnd();
}
private bool _autoScroll = true;
private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.ExtentHeightChange == 0)
{
_autoScroll = ScrollViewer.VerticalOffset == ScrollViewer.ScrollableHeight;
}
if (_autoScroll && e.ExtentHeightChange != 0)
{
ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);
}
}
You can use a TextBox and set the VerticalAlignment to Stretch in your Xaml file.
< TextBox x:Name="textbox" VerticalAlignment="Stretch">
Is it possible to expose a public event from my ViewModel is such a way as to allow it to be bound to a custom DependencyProperty in my View?
My application is written in C# using the .NET 4.5 framework. It has a MVVM architecture with no code-behind in the view and custom DependencyProperty classes to bind WPF-specific behvaiours of the View to properties exposed by the ViewModel.
There is a set of properties that I would like the ViewModel to be able to expose that represent events to which the View needs to respond. For example, when a top level ViewModel object is about to be Disposed I would like the WPF View implementation to respond by closing the corresponding Window. This could occur when a configuration process has displayed a Dialog Window, the user has enetered and confirmed the information and the ViewModel has passed it to the Model and is no longer required.
I am aware that there are many questions that are specific to solving the 'show dialog from ViewModel' question; this is not one of them and I have a solution to that one.
I've read through the MSDN documentation for DependencyProperties and can't find anything specific to binding to event properties.
What I would like to achieve is something similar to the code below. This code builds, but results in a typical System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found error when the MainWindow is shown.
I am aware that there are many questions that go along the lines of 'please help me debug my System.Windows.Data Error: 40 issue'; this is (probably) not one of these either. (But I'd be happy if that's all it really is.)
Source for the custom DependencyProperty in WindowBindableProperties.cs:
using System;
using System.Threading;
using System.Windows;
namespace WpfEventBinding
{
public static class WindowBindableProperties
{
#region ViewModelTerminatingEventProperty
/// <summary>
/// Register the ViewModelTerminatingEvent custom DependencyProperty.
/// </summary>
private static DependencyProperty _viewModelTerminatingEventProperty =
DependencyProperty.RegisterAttached
(
"ViewModelTerminatingEvent",
typeof(ViewModelTerminatingEventHandler),
typeof(WindowBindableProperties),
new PropertyMetadata(null, ViewModelTerminatingEventPropertyChanged)
);
/// <summary>
/// Identifies the ViewModelTerminatingEvent dependency property.
/// </summary>
public static DependencyProperty ViewModelTerminatingEventProperty
{ get { return _viewModelTerminatingEventProperty; } }
/// <summary>
/// Gets the attached ViewModelTerminatingEvent dependecy property.
/// </summary>
/// <param name="dependencyObject">The window attached to the WindowViewModel.</param>
/// <returns>The ViewModelTerminatingEventHandler bound to this property</returns>
public static ViewModelTerminatingEventHandler GetViewModelTerminatingEvent
(DependencyObject dependencyObject)
{
return (dependencyObject.GetValue(ViewModelTerminatingEventProperty)
as ViewModelTerminatingEventHandler);
}
/// <summary>
/// Sets the ViewModelTerminatingEvent dependency property.
/// </summary>
public static void SetViewModelTerminatingEvent(
DependencyObject dependencyObject,
ViewModelTerminatingEventHandler value)
{
dependencyObject.SetValue(ViewModelTerminatingEventProperty, value);
}
/// <summary>
/// Gets the ViewModelTerminatingEvent dependency property.
/// </summary>
private static void ViewModelTerminatingEventPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Window instance = d as Window;
if (null != instance)
{
if (null != e.OldValue)
{
throw new System.InvalidOperationException(
"ViewModelTerminatingEvent dependency property cannot be changed.");
}
if (null != e.NewValue)
{
// Attach the Window.Close() method to the ViewModel's event
var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
}
}
}
#endregion
}
}
Source for MainWindow.xaml:
(This example contains code-behind to simplify the Stop Button implementation.)
<Window x:Class="WpfEventBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfEventBinding"
v:WindowBindableProperties.ViewModelTerminatingEvent="{Binding Path=RequestCloseEvent}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="{Binding Path=CloseCommandName}" Click="StopButton_Click" ></Button>
</Grid>
</Window>
Source for MainWindow.xaml.cs (code behind):
using System.Windows;
namespace WpfEventBinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
MainWindowViewModel vm = (DataContext as MainWindowViewModel);
if (null != vm)
{
vm.Stop();
}
}
}
}
Source for the MainWindowViewModel.cs:
using System;
using System.ComponentModel;
namespace WpfEventBinding
{
public delegate void ViewModelTerminatingEventHandler();
class MainWindowViewModel
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Raised by the ViewModel to indicate to the view that it is no longer required.
// Causes System.Windows.Data Error: 40 : BindingExpression path error. Is it
// Possible to bind to an 'event' property?
public event ViewModelTerminatingEventHandler RequestCloseEvent;
// This has to have the public 'get' to allow binding. Is there some way to
// do the same thing for the 'event'?
public String CloseCommandName { get; private set; }
public MainWindowViewModel()
{
CloseCommandName = "Close";
}
internal void Stop()
{
ViewModelTerminatingEventHandler RaiseRequestCloseEvent =
RequestCloseEvent;
if (null != RaiseRequestCloseEvent)
{
RaiseRequestCloseEvent();
}
}
internal void Start()
{
OnPropertyChanged("CloseCommandName");
OnPropertyChanged("ViewModelTerminatingEvent");
}
private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler RaisePropertyChangedEvent = PropertyChanged;
if (RaisePropertyChangedEvent != null)
{
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
RaisePropertyChangedEvent(this, propertyChangedEventArgs);
}
}
}
}
Source for App.xaml:
<Application x:Class="WpfEventBinding.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
<!-- Nothing to see here. Move along... -->
</Application.Resources>
</Application>
Source for App.xaml.cs
using System.Windows;
namespace WpfEventBinding
{
public partial class App : Application
{
public App()
{
Startup += new StartupEventHandler(App_Startup);
}
void App_Startup(object sender, StartupEventArgs e)
{
MainWindowViewModel vm = new MainWindowViewModel();
MainWindow window = new MainWindow();
// Make sure this is set before attempting binding!
window.DataContext = vm;
vm.Start();
window.Show();
}
}
}
It appears that the public event ViewModelTerminatingEventHandler RequestCloseEvent; syntax is not sufficient to allo the data binding to occur. A similar problem is see if the public String CloseCommandName { get; private set; } is declared as public String CloseCommandName; without the { get; private set; }. However, there is no { get; private set; } for events, which use the {add{} remove{}} syntax (and that does not solve the problem either).
Is what I'm attempting possible and if so, what have I missed?
View closing means window closing event. So you basically want react on events in the view. I read recently this arcticle, there was a very good image
and also mentioned EventBehavior existence.
Your best bet, if you don't want any code behind, is to use behaviors. Behavior is a simple attached property, which can perform actions, to example rising application-wide commands, which ViewModel can then catch without MVVM issues.
Here is an example of behavior:
public static class FreezeBehavior
{
public static bool GetIsFrozen(DependencyObject obj)
{
return (bool)obj.GetValue(IsFrozenProperty);
}
public static void SetIsFrozen(DependencyObject obj, bool value)
{
obj.SetValue(IsFrozenProperty, value);
}
public static readonly DependencyProperty IsFrozenProperty =
DependencyProperty.RegisterAttached("IsFrozen", typeof(bool), typeof(FreezeBehavior), new PropertyMetadata(OnIsFrozenChanged));
private static void OnIsFrozenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var freezable = d as Freezable;
if (freezable != null && freezable.CanFreeze)
freezable.Freeze();
}
}
}
it's used like this
<DropShadowEffect ShadowDepth="2" local:FreezeBehavior.IsFrozen="True"/>
It can be attached to any freezable to freeze it. In your case you want to subscribe to event and invoke command or set property, or whatever to inform ViewModel.
What you are asking for is kinda weird, but I'm not going to get into a big long discussion about that....
You don't bind to events - you expose them and the view can add handlers for the events.
Of course this means you will have to put some code behind into the view - but this is fine provided it is UI related. To complete the decoupling your view should only handle the viewmodel as an interface, this means you can easily swap out viewmodels at a later stage.
(Note that I've avoided talking about event triggers).
How can I disable the navigation shortcuts in a frame (for example the "Backspace" for navigation backward and "Alt+Right arrow" for navigation forward).
I want to use other keyboard functions, so I want to disable the navigation shortcuts of the frame.
Who can help me?
there is a more elegant solution where Attached behaviours can be used to disable navigation without actually extending a frame.
create an attached-behaviour :
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace A
{
public static class DisableNavigation
{
public static bool GetDisable(DependencyObject o)
{
return (bool)o.GetValue(DisableProperty);
}
public static void SetDisable(DependencyObject o, bool value)
{
o.SetValue(DisableProperty, value);
}
public static readonly DependencyProperty DisableProperty =
DependencyProperty.RegisterAttached("Disable", typeof(bool), typeof(DisableNavigation),
new PropertyMetadata(false, DisableChanged));
public static void DisableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var frame = (Frame)sender;
frame.Navigated += DontNavigate;
frame.NavigationUIVisibility = NavigationUIVisibility.Hidden;
}
public static void DontNavigate(object sender, NavigationEventArgs e)
{
((Frame)sender).NavigationService.RemoveBackEntry();
}
}
}
And in the xaml add this whenever you use a frame :
<Frame beha:DisableNavigation.Disable="True" />
and at the top of the xaml add the import :
xmlns:beha="clr-namespace:A"
See this answer for how to disable the keyboard shortcuts:
Disable backspace in wpf
That doesn't work for the back and forward navigation mouse buttons. To prevent that, it seems you need to put a handler on the Navigating event and cancel it if you don't want it.
For example, to totally disable forward navigation:
In .xaml:
<Frame Navigating="HandleNavigating" />
In code behind:
void HandleNavigating(Object sender, NavigatingCancelEventArgs e)
{
if (e.NavigationMode == NavigationMode.Forward)
{
e.Cancel = true;
}
}
What I do is host the content in ContentControl.
The real answer to disable all shortcuts in WPF Frame is:
foreach (var vNavigationCommand in new RoutedUICommand[]
{ NavigationCommands.BrowseBack,
NavigationCommands.BrowseForward,
NavigationCommands.BrowseHome,
NavigationCommands.BrowseStop,
NavigationCommands.Refresh,
NavigationCommands.Favorites,
NavigationCommands.Search,
NavigationCommands.IncreaseZoom,
NavigationCommands.DecreaseZoom,
NavigationCommands.Zoom,
NavigationCommands.NextPage,
NavigationCommands.PreviousPage,
NavigationCommands.FirstPage,
NavigationCommands.LastPage,
NavigationCommands.GoToPage,
NavigationCommands.NavigateJournal })
{
ctlFrame.CommandBindings.Add(new CommandBinding(vNavigationCommand, (sender, args) => { }));
}
The frame it's self provides no method of disabling navigation. It only provides a means to hide the navigation controls. You can however inherit from the Frame class and make some modifications to it yourself. The following example removes the last page from the BackStack every time the page navigates. Thus ensuring the frame can never navigate backwards as it does not know which page was last.
class NoNavFrame : Frame
{
public NoNavFrame()
{
this.Navigated += new System.Windows.Navigation.NavigatedEventHandler(NoNavFrame_Navigated);
}
void NoNavFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
this.NavigationService.RemoveBackEntry();
}
}
Then you can include this in XAML as follows...
<myControls:NoNavFrame x:Name="myFrame" NavigationUIVisibility="Hidden" />