I started an WPF application in a mvvm way. The main window contains a frame-control for navigating through differnt pages. For this I use a simple NavigationService for now:
public class NavigationService : INavigationService
{
private Frame _mainFrame;
#region INavigationService Member
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri uri)
{
if(EnsureMainFrame())
{
_mainFrame.Navigate(uri);
}
}
public void GoBack()
{
if(EnsureMainFrame() && _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}
}
#endregion
private bool EnsureMainFrame()
{
if(_mainFrame != null)
{
return true;
}
var mainWindow = (System.Windows.Application.Current.MainWindow as MainWindow);
if(mainWindow != null)
{
_mainFrame = mainWindow.NavigationFrame;
if(_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
}
return false;
}
}
On Page1 a button press forces the navigation to Page2 using th NavigationService.
On Page2 there is a TextBox. If the TextBox is focused i can use ALT + left arrow key to navigate back to Page1. How can I disable this behavior?
I tried setting KeyboardNavigation.DirectionalNavigation="None" in the frame-control and also in the TextBox-Control without success.
Add the following event handler to the textbox to disable the alt + left navigation:
private void textBox1_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
&& (Keyboard.IsKeyDown(Key.Left)))
{
e.Handled = true;
}
}
XAML
<TextBox ... KeyDown="textBox1_PreviewKeyDown" />
EDIT: changed to PreviewKeyDown in order to capture arrow key events
Related
Let's say you want to prevent the user from navigating away from your Xamarin.Forms.WebView to an external page.
public App ()
{
var webView = new WebView
{
Source = new HtmlWebViewSource
{
Html = "<h1>Hello world</h1><a href='http://example.com'>Can't escape!</a><iframe width='420' height='315' src='https://www.youtube.com/embed/oHg5SJYRHA0' frameborder='0' allowfullscreen></iframe>"
}
};
webView.Navigating += WebView_Navigating;
MainPage = new ContentPage {
Content = webView
};
}
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
// we don't want to navigate away from our page
// open it in a new page instead etc.
e.Cancel = true;
}
This works fine on Windows and Android. But on iOS, it doesn't load at all!
On iOS, the Navigating event gets raised even when loading the source from a HtmlWebViewSource, with a URL that looks something like file:///Users/[user]/Library/Developer/CoreSimulator/Devices/[deviceID]/data/Containers/Bundle/Application/[appID]/[appName].app/
Alright, so you can get around that with something like this:
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
if (e.Url.StartsWith("file:") == false)
e.Cancel = true;
}
The page finally loads on iOS. Yay. But wait! The embedded YouTube video doesn't load! That's because the Navigating event gets raised for the internal navigation of embedded resources like iframes and even external scripts (like Twitter's <script charset="utf-8" type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>), but only on iOS!
I couldn't find a way to determine if the Navigating event was raised from internal navigation or because the user clicked a link.
How to get around this?
I am not sure if it is possible to detect in Xamarin Forms out of the box but the navigation type is easily determined using a custom renderer. In your custom iOS renderer, assign a WebViewDelegate and within that Delegate class, override ShouldStartLoad() like so:
public class CustomWebViewRenderer : WebViewRenderer {
#region Properties
public CustomWebView CustomWebViewItem { get { return Element as CustomWebView; } }
#endregion
protected override void OnElementChanged(VisualElementChangedEventArgs e) {
base.OnElementChanged(e);
if(e.OldElement == null) {
Delegate = new CustomWebViewDelegate(); //Assigning the delegate
}
}
}
internal class CustomWebViewDelegate : UIWebViewDelegate {
public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
if(navigationType == UIWebViewNavigationType.LinkClicked) {
//To prevent navigation when a link is click, return false
return false;
}
return true;
}
}
You could also surface a bool property or even an enum back up to your Xamarin Forms WebView which would say whether the Navigating event was from a link being clicked or from something else, though a custom renderer would be needed for that as well.
private bool isNavigated = false;
public CustomWebView()
{
if (Device.OS == TargetPlatform.Android)
{
// always true for android
isNavigated = true;
}
Navigated += (sender, e) =>
{
isNavigated = true;
};
Navigating += (sender, e) =>
{
if (isNavigated)
{
try
{
var uri = new Uri(e.Url);
Device.OpenUri(uri);
}
catch (Exception)
{
}
e.Cancel = true;
}
};
}
In my WinForms application written in C# there is a Button on one Form which needs to slightly alter the appearance of a second Form (just change the Text on a Button).
I have managed to do this, but the code is horribly long, and I believe there must be a much more concise way of achieving the same thing.
Here is my code for the Button on Form frmConflicts and how it changes the Text on the Button btnAddCase on Form frmAdmin (works, but seems too long) -
private void btnNoConflicts_Click(object sender, EventArgs e)
{
try
{
foreach (Form f in Application.OpenForms)
{
if (f.Name == "frmAdmin")
{
frmAdmin a = (frmAdmin)f;
a.conflictsClear = true;
foreach (Control ctrl in a.Controls)
{
if (ctrl.Name == "panAdmin")
{
foreach (Control ctrl2 in ctrl.Controls)
{
if (ctrl2.Name == "tabControlAdmin")
{
TabControl tab = (TabControl)ctrl2;
foreach(TabPage page in tab.TabPages)
{
if (page.Name == "pageNewCase")
{
foreach (Control ctrl3 in page.Controls)
{
if (ctrl3.Name == "panCaseDetails")
{
foreach (Control ctrl4 in ctrl3.Controls)
{
if (ctrl4.Name == "btnAddCase")
{
ctrl4.Text = "Add Case";
}
}
}
}
}
}
}
}
}
}
}
}
this.Close();
}
catch (Exception eX)
{
MessageBox.Show("frmConflicts: btnNoConflicts()" + Environment.NewLine + eX.Message);
}
Any help to significantly reduce the amount of code would be much appreciated, as I am going to need to do similar interactions between Forms elsewhere in my application.
If your button is added through designer and is not dynamically created the solution is simple: add a method inside your frmAdmin like
public void ChangeCaption(string caption)
{
btnAddCase.Text = "Add case";
}
and then
var frmAdmin = Application.OpenForms.OfType<Form>().FirstOrDefault(x => x.GetType() == typeof(frmAdmin));
if (frmAdmin != null)
{
frmAdmin.ChangeCaption("Add case");
}
I think it`s help to you
foreach (Form f in Application.OpenForms)
{
var controls =this.Controls.Find("btnAddCase", true);
if(controls!=null)
foreach(var control in controls)
{
control.Text="Add case";
}
}
If the the appearance of second from require a change on first from you should solve this in another way.
The best is that your button that require a change should be open for capture the event of form two open and then apply required change.
In the place where you declare your button you should assign to it a listener that will capture the Form2 opening and then apply action.
so in the method private void btnNoConflicts_Click(object sender, EventArgs e) you should trigger event for that button to capture instead off searching it.
You could use LINQ + ControlCollection.Find:
Control btnAddCase = Application.OpenForms.Cast<Form>()
.Where(f => f.Name == "frmAdmin")
.SelectMany(f => f.Controls.Find("btnAddCase", true)) // true means recursive
.FirstOrDefault();
if(btnAddCase != null)
btnAddCase.Text = "Add Case";
You could create a public property and subscribe to a PropertyChanged event from your form, you'll need your class that has the public variable to extend INotifyPropertyChanged.
//Your class
public class ButtonText : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonValue
{
get{ return _buttonText; }
set
{
//Sets the value of _buttonText to the value passed in an argument
_buttonText = value;
RaisePropertyChanged("ButtonValue");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In your form class you'd bind to the property ButtonValue property of the ButtonText class like so:
ButtonText buttonObj = new ButtonText();
//Property field to bind, object to bind, property to bind
btnAddCase.DataBindings.Add("Text", buttonObj,"ButtonValue");
buttonObj.ButtonText = "Your text to bind.";
Because the btnAddCase.Text property is bound to the ButtonValue property of the ButtonText class, your btnAddCase.Text property will reflect the value of your ButtonText.ButtonValue property at all times, it's also a two way binding.
I have many controls in a window. Requirement is to know which control gets the focus from the lost focus event of a control.
Say, A Text box and it has the focus. Now I am clicking a button. while doing this, need to know that i am moving the focus to button from the Text box lost focus event.
So how could i achieve this..
This is what I did and its working for me
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
lostFocusControl = e.OldFocus;
}
private void PauseBttn_PreviewKeyDown(object sender, KeyEventArgs e)
{
/**invoke OnPreviewLostKeyboardFocus handller**/
}
Hope it will help
You can use FocusManager to handle this,
In your LostFocusEvent, Use FocusManager.GetFocusedElement()
uiElement.LostFocus+=(o,e)=>
{
var foo=FocusManager.GetFocusedElement();
}
The following class watches the FocusManager for changes in focus, it's a looped thread so you have to put up with the fact that it's running but when focus changes it will just raise an event letting you know what changed.
Just add these two classes to your project.
public class FocusNotifierEventArgs : EventArgs
{
public object OldObject { get; set; }
public object NewObject { get; set; }
}
public class FocusNotifier : IDisposable
{
public event EventHandler<FocusNotifierEventArgs> OnFocusChanged;
bool isDisposed;
Thread focusWatcher;
Dispatcher dispatcher;
DependencyObject inputScope;
int tickInterval;
public FocusNotifier(DependencyObject inputScope, int tickInterval = 10)
{
this.dispatcher = inputScope.Dispatcher;
this.inputScope = inputScope;
this.tickInterval = tickInterval;
focusWatcher = new Thread(new ThreadStart(FocusWatcherLoop))
{
Priority = ThreadPriority.BelowNormal,
Name = "FocusWatcher"
};
focusWatcher.Start();
}
IInputElement getCurrentFocus()
{
IInputElement results = null;
Monitor.Enter(focusWatcher);
dispatcher.BeginInvoke(new Action(() =>
{
Monitor.Enter(focusWatcher);
results = FocusManager.GetFocusedElement(inputScope);
Monitor.Pulse(focusWatcher);
Monitor.Exit(focusWatcher);
}));
Monitor.Wait(focusWatcher);
Monitor.Exit(focusWatcher);
return results;
}
void FocusWatcherLoop()
{
object oldObject = null;
while (!isDisposed)
{
var currentFocus = getCurrentFocus();
if (currentFocus != null)
{
if (OnFocusChanged != null)
dispatcher.BeginInvoke(OnFocusChanged, new object[]{ this, new FocusNotifierEventArgs()
{
OldObject = oldObject,
NewObject = currentFocus
}});
oldObject = currentFocus;
}
}
Thread.Sleep(tickInterval);
}
}
public void Dispose()
{
if (!isDisposed)
{
isDisposed = true;
}
}
}
Then in your code behind, create a new instance of the Focus Notifier class and hook on to it's OnFocusChanged event, remember to dispose it at the end or the thread will keep your app open.
public partial class MainWindow : Window
{
FocusNotifier focusNotifier;
public MainWindow()
{
InitializeComponent();
focusNotifier = new FocusNotifier(this);
focusNotifier.OnFocusChanged += focusNotifier_OnFocusChanged;
}
void focusNotifier_OnFocusChanged(object sender, FocusNotifierEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.OldObject);
System.Diagnostics.Debug.WriteLine(e.NewObject);
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
focusNotifier.Dispose();
base.OnClosing(e);
}
}
have you tried to register your controls to Control.LostFocus event and there you can check for Form.ActiveControl, to determine which control currently has the focus
I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.
I have a Windows Forms application with some buttons for the F keys. When you place the mouse over the buttons the get grey, and when you click they get a slightly lighyer grey. I would like to mimic that behaviour with F key keystrokes... how would you do it?
Set the Form's KeyPreview property to true, handle the KeyDown and KeyUp events, track which function key(s) are pressed, and call the Invalidate method on the button for each key the went down or up.
Then, handle the button's Paint event, and, if its key is down, use the ButtonRenderer class to draw the button as if it were pressed.
Use Button.PerformClick().
Finally I implemented the button changing the background:
class FunctionButton : Button
{
private Color m_colorOver;
private bool m_isPressed;
public FunctionButton() : base()
{
m_isPressed = false;
}
protected override void OnGotFocus(EventArgs e)
{
OnMouseEnter(null);
base.OnGotFocus(e);
}
protected override void OnLostFocus(EventArgs e)
{
if (!m_isPressed)
{
OnMouseLeave(null);
}
base.OnLostFocus(e);
}
protected override void OnMouseLeave(EventArgs e)
{
if (!Focused && !m_isPressed)
{
base.OnMouseLeave(e);
}
}
public void FunctionKeyPressed()
{
// Handle just the first event
if (!m_isPressed)
{
m_isPressed = true;
m_colorOver = FlatAppearance.MouseOverBackColor;
FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
OnMouseEnter(null);
PerformClick();
}
}
public void FunctionKeyReleased()
{
m_isPressed = false;
FlatAppearance.MouseOverBackColor = m_colorOver;
if (Focused)
{
OnMouseEnter(null);
}
else
{
base.OnMouseLeave(null);
}
}
}
It is not the most clean way but it works fine. I would like more examples doing this with a cleaner and more elegant style.
SetCapture and ReleaseCapture might work.