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
Related
I created an ObservableCollectionEx.cs class that inherits the ObservableCollection class to suppress notifications while the collection is being updated until it's done updating from the answer here.
The class:
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
private bool _notificationSupressed = false;
private bool _supressNotification = false;
public bool SupressNotification
{
get
{
return _supressNotification;
}
set
{
_supressNotification = value;
if (_supressNotification == false && _notificationSupressed)
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
_notificationSupressed = false;
}
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SupressNotification)
{
_notificationSupressed = true;
return;
}
base.OnCollectionChanged(e);
}
}
A collection of models is created in a class that is meant to update in response to a number of events. One is based on an observable sequence that simply updates the collection at an interval and another is based on a button click event. Stepping through the codes, I see that both events are causing the collection to update successfully, but only the button click causes the WPF ListView to be notified and updated accordingly. The UI is a WPF UserControl that is used to create a CustomTaskPane in Microsoft Word using VSTO.
The code that updates the collection via Observable sequence:
public partial class CrossReferenceControl : UserControl, ICrossReferenceControl
{
private ICrossReferenceControlViewModel referenceControlViewModel;
private IOpenDocumentModel OpenDocumentModel;
private ICrossReferenceGuy CrossReferenceGuy;
private bool isOpen;
private IObservable<bool> openDocModelUpdateObservable;
private static TimeSpan period = TimeSpan.FromSeconds(20);
private IObservable<long> observable = Observable.Interval(period);
public readonly Subject<bool> OpenDocModelUpdateActionSubject = new Subject<bool>();
public ICrossReferenceControlViewModel ReferenceControlViewModel => referenceControlViewModel;
public bool IsOpen
{
get { return isOpen; }
set { isOpen = value; }
}
public CrossReferenceControl(IOpenDocumentModel openDocumentModel, ICrossReferenceControlViewModel referenceControlViewModel, ICrossReferenceGuy crossReferenceGuy)
{
InitializeComponent();
this.referenceControlViewModel = referenceControlViewModel;
OpenDocumentModel = openDocumentModel;
CrossReferenceGuy = crossReferenceGuy;
//CrossReferenceControlViewModel controlViewModel = new CrossReferenceControlViewModel((OpenDocumentModel)openDocumentModel);
DataContext = referenceControlViewModel;
observable.Subscribe(O => OpenDocumentModel.UpdateCaptionsSubject.OnNext(IsOpen));
}
}
The code that updates via button click event (this works fine):
private void ButtonRefresh_Click(object sender, RoutedEventArgs e)
{
OpenDocumentModel.UpdateCaptionsSubject.OnNext(IsOpen);
}
Note: The codes are cut down to provide only what I think is essential.
I have make a ContentControl and it has some custom Propertities. The control itself works fine but I like to update its interface during design time in XAML editor. The problem is next: The control's UI update if I change its Size (SizeChanged event will do that) but I cannot find any way to do this if CustomProperty like OffsetX changes during design time.
So, how to change the following code to make this happen? It isn't too convenient to update Control UI changing its size every time.
public sealed class MyControlElement: ContentControl
{
//
//SOME INITIALIZE CODE IS HERE
//
public MyControlElement() => DefaultStyleKey = typeof(MyControlElement);
protected override void OnApplyTemplate()
{
//
//SOME INITIALIZE CODE IS HERE
//
base.OnApplyTemplate();
}
//OFFSET X DESCRIPTION
[Description("OffsetX"), Category("MyControlElementParameters"), Browsable(true)]
//OFFSET X
public int OffsetX
{
get
{
return (int)GetValue(OffsetXProperty);
}
set
{
if (OffsetX != value)
{
SetValue(OffsetXProperty, value);
OnOffsetXChanged(this, new EventArgs());
}
}
}
public static readonly DependencyProperty OffsetXProperty = DependencyProperty.Register("OffsetX", typeof(int), typeof(MyControlElement), PropertyMetadata.Create(0));
public event EventHandler OffsetXChanged;
private void OnOffsetXChanged(object sender, EventArgs e)
{
UpdateControlUI();
this.OffsetXChanged?.Invoke(this, e);
}
}
I found some kind of "Hack". Still hoping to find better solution. The next trick works and it is possible to update Control interface during design time.
First need to add handler for Loaded.
public MyControlElement()
{
this.DefaultStyleKey = typeof(MyControlElement);
this.Loaded += MyControlElement_Loaded;
}
private void MyControlElement_Loaded(object sender, RoutedEventArgs e)
{
//
//SOME INITIALIZE CODE HERE IF NEEDED
//
//RUN CONTROL VISUAL UPDATER ONLY IF IN DESIGN MODE
if (DesignMode.DesignModeEnabled) ControlDesignTimeUIUpdater();
//FLAG - CONTROL HAS BEEN INITIALIZED
IsControlInitialized = true;
}
And lets add ControlDesignTimeUIUpdater void for UI update. This void has a loop to keep UI updated during design time.
private async void ControlDesignTimeUIUpdater()
{
double OldImageWidth = ImageWidth;
double OldImageHeight = ImageHeight;
CornerRadius OldImageCornerRadius = ImageCornerRadius;
double OldBorderThickness = BorderThickness;
ImageSource OldMyImageSource = MyImageSource;
while (this.IsLoaded)
{
//CHECK CHANGES DELAY 100ms
await Task.Delay(100);
//MAKE SURE CONTROL IS INITIALIZED BEFORE ANY UI UPDATES
if (IsControlInitialized)
{
if (OldImageWidth != ImageWidth)
{
OldImageWidth = ImageWidth;
SetImageWidth();
}
if (OldImageHeight != ImageHeight)
{
OldImageHeight = ImageHeight;
SetImageHeight();
}
if (OldImageCornerRadius != ImageCornerRadius)
{
OldImageCornerRadius = ImageCornerRadius;
SetImageCornerRadius();
}
if (OldBorderThickness != BorderThickness)
{
OldBorderThickness = BorderThickness;
SetBorderThickness();
}
if (OldMyImageSource != MyImageSource)
{
OldMyImageSource = MyImageSource;
SetMyImageSource();
}
//
// ETC.
//
}
}
}
By this Hack it is possible update control in "real-time" during design. It's even possible add animations, size changes etc.
I have a ListView that changes. This ListView is inside LinearLayout that also has an Icon that shows as a checkmark if the ListView items include an item of a certain type. It shows an "X" if none of the items are of that type.
In the code below, the Console.WriteLine works.
How do I update the Icon (aka call the Redraw function) after a NotifyDataSetChanged has been called on the ListView adapter. The function is outside of the scope of the observer and cannot be called inside the OnChanged.
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
}
private void Redraw () {
// UPDATE ICON HERE
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
// This area is hit, but how do I call the Redraw method above? It is out of scope
}
}
/// EDIT: Something I've Tried THAT WORKS! Anything seem off about it?
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
// Added the last four event handler pieces
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
g.ToastShort("Change was observerd");
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}
You can use messaging-center to notify your activity to call Redraw() when OnChanged hit.
The MessagingCenter is a simple way to reduce coupling, especially
between view models. It can be used to send and receive simple
messages or pass an argument between classes. Classes should
unsubscribe from messages they no longer wish to receive.
In the OnChanged(), send a message every time it is hit:
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
// This area is hit, but how do I call the Redraw method above? It is out of scope
MessagingCenter.Send<object>(this, "needRedraw");
}
In your Init(), Subscribe the needRedraw message and call redraw whenever the "needRedraw" message is sent:
private void Init()
{
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
MessagingCenter.Subscribe<object>(this, "needRedraw", (sender) => {
// do something whenever the "needRedraw" message is sent
Redraw();
});
}
Thank you #Tyddlywink for your comment: "youll need to create [an Event] in your MyDataSetObserver class and fire it"
I used this as a resource for adding Events: https://learn.microsoft.com/en-us/dotnet/standard/events/
Here are the updates I added to trigger my Redraw() function:
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
// To be honest, I don't know what int DataChanged wants.. so arbitrarily set it to 1.
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}
I have created a custom menu item which appears in the default menu which pops up when selecting text on my custom WebView.
On clicking on the menu item it calls EvaluateJavascript to get the selected WebView text, and then passes the text to another page.
However after performing this action once or twice, some text on certain areas of the screen start to become unresponsive to clicks eg. text on the parts of the WebView become unselectable, clicks on that part of the screen on other pages becomes unresponsive and even the soft keyboard becomes unclickable in some spots. If this continues for a while sometimes my app will then suddenly freeze the entire operating system and I have to soft reset my phone. It appears that there maybe some serious memory leakage going on.
I create my custom menu item in the MainActivity class:
public override void OnActionModeStarted(ActionMode mode)
{
if (Root.IsCurrentPageType<DictPage>() && DictP.IsWebViewFocused())
{
IMenu menu = mode.Menu;
menu.Add("To Notes");
menu.GetItem(0).SetOnMenuItemClickListener(new MyMenuItemOnMenuItemClickListener(this, mode));
}
base.OnActionModeStarted(mode);
}
It is then handled in the Listener class...
public class MyMenuItemOnMenuItemClickListener : Java.Lang.Object, IMenuItemOnMenuItemClickListener
{
private MainActivity mContext;
ActionMode _mode;
public MyMenuItemOnMenuItemClickListener(MainActivity activity, ActionMode mode)
{
this.mContext = activity;
_mode = mode;
}
public bool OnMenuItemClick(IMenuItem item)
{
WEB.CopyToMainNotes();
Device.BeginInvokeOnMainThread(() =>
{
//close menu if clicked
_mode?.Finish();
});
return true;
}
}
...which calls CopyToMainNotes on my derived WebView class and its associated Renderer and EventHandler classes:
public class WebViewEx : Xamarin.Forms.WebView
{
public static WebViewEx WEB;
//Namespace
//YourClass
public event WebViewExEventHandler CallNativeMethodEvent;
public void CallNativeMethod(WebViewExEventType type)
{
WebViewExEventArgs e = new WebViewExEventArgs();
e.EventType = type;
CallNativeMethodEvent?.Invoke(this, e);
}
public WebViewEx()
{
WEB = this;
}
public void CopyToMainNotes()
{
Device.BeginInvokeOnMainThread(() =>
{
CallNativeMethod(WebViewExEventType.copyToMainNotes);
});
}
}
public delegate void WebViewExEventHandler(object sender, WebViewExEventArgs e);
public class WebViewExEventArgs : EventArgs
{
public enum WebViewExEventType { copyToMainNotes };
public WebViewExEventType EventType = WebViewExEventType.copyToMainNotes;
public WebViewExEventArgs() : base()
{
}
}
public class WebViewExRenderer : WebViewRenderer
{
public WebViewExRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
WebViewEx ex = e.NewElement as WebViewEx;
ex.CallNativeMethodEvent += WebViewEx_CallNativeMethodEvent;
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
public JavascriptCallback(Action<string> callback)
{
_callback = callback;
}
private Action<string> _callback;
public void OnReceiveValue(Java.Lang.Object value)
{
_callback?.Invoke(Convert.ToString(value));
}
}
private void WebViewEx_CallNativeMethodEvent(object sender, WebViewExEventArgs e)
{
switch (e.EventType)
{
case WebViewExEventType.copyToMainNotes:
{
CopyToMainNotes();
break;
}
}
}
public void CopyToMainNotes()
{
string script = "(function(){ return window.getSelection().toString()})()";
var response = string.Empty;
Control?.EvaluateJavascript(script, new JavascriptCallback((r) =>
{
response = r;
Device.BeginInvokeOnMainThread(() =>
{
DPage.CopyThisTextToAnotherPage(response.ToString().Trim('\"'));
});
}));
}
}
The CopyToMainNotes method above is where the EvaluateJavascript takes place and the selected text finally gets sent to another page.
Any ideas where I might be going wrong here? Thanks in advance!
I have one MainControl that contains a ChildControl. The ChildControl has a hide button that would hide itself.
When hidden I expect the MainControl to hook the event and dispose it.
MainControl
ChildControl > Hide button
Can't figure out how I should hook those.
Any tip? Thank you!
You can create an event that will notify the main control that the child control is hidden, and in your main control, handling the event, you can dispose of your control.
Below is a small sample code of how you can go about creating your event for the hidden action.
class MainControl
{
ChildControl childControl;
public MainControl()
{
childControl = new ChildControl();
childControl.VisibilityChanged += childControl_VisibilityChanged;
}
void childControl_VisibilityChanged(object sender, HiddenEvent e)
{
if (e.isHidden)
{
//close control here
}
}
}
public class HiddenEvent : EventArgs
{
public HiddenEvent(bool propertyValue)
{
this.isHidden = propertyValue;
}
public bool isHidden { get; set; }
}
public class ChildControl
{
public event EventHandler<HiddenEvent> VisibilityChanged;
public ChildControl()
{
}
private bool _isHidden;
public bool Control
{
get
{
return _isHidden;
}
set
{
_isHidden = value;
Hidden_Handler(value);
}
}
private void Hidden_Handler(bool isHidden)
{
var handler = VisibilityChanged;
if (handler != null)
VisibilityChanged(this, new HiddenEvent(isHidden));
}
}
As an option you could bind ChildControl's button to a remove command on the main control (using RelativeSource) and let MainControl do all the work