Binding FocusAction Behavior on Button Click - c#

I am not very experienced using Behaviors, but so far they have come in handy to be able to execute code from the ViewModel but still triggering the actions from the View.
In my current scenario, I have a TextBox that appears when a Button is clicked. I would like to then set the focus of that TextBox after the Button has been clicked.
Previously, I have been able to set the focus using an EventTriggerBehavior like so:
<core:EventTriggerBehavior>
<behaviors:FocusAction />
</core:EventTriggerBehavior>
However, this will only suffice if I want to set the focus of that control when the View is loaded. In this case, the TextBox isn't visible at that time, and actually, the focus goes to a different TextBox initially.
Is there a way to set the focus of a control from the ViewModel? This is a WinRT 8.1 application, but will be being ported to Windows 10 Universal in the future.
EDIT
The answer here looks like it would do what I am looking for, but when I try it I get an error:
Cannot resolve symbol 'UIPropertyMetadata'
From what I can tell, that class exists in the System.Windows namespace, but even with a using System.Windows; I still get the same error. I have also tried new System.Windows.UIPropertyMetadata(null, ElementToFocusPropertyChanged) as well, but that doesn't make a difference either. Is that class not available in WinRT?

I was able to get it to work by slightly modifying the answer linked to in my original question. For anyone looking to accomplish this in a WinRT application, here is the modified code:
public class EventFocusAttachment
{
public static Control GetElementToFocus(Button button)
{
return (Control)button.GetValue(ElementToFocusProperty);
}
public static void SetElementToFocus(Button button, Control value)
{
button.SetValue(ElementToFocusProperty, value);
}
public static readonly DependencyProperty ElementToFocusProperty =
DependencyProperty.RegisterAttached("ElementToFocus", typeof(Control),
typeof(EventFocusAttachment), new PropertyMetadata(null, ElementToFocusPropertyChanged));
public static void ElementToFocusPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
button.Click += async (s, args) =>
{
var control = GetElementToFocus(button);
if (control != null)
{
await Task.Delay(100);
control.Focus(FocusState.Programmatic);
}
};
}
}
}
I had to add a small delay because the TextBox that I am focusing on is not visible until after the button is pressed, so it wasn't working without the delay. Also, I had to change the UIPropertyMetadata to simply PropertyMetadata, and add async to the lambda expression to allow for the await Task.Delay(100);

Related

Can a ContentView receive focus?

I have an element that I want to become highlighted when navigated to with a D-pad and respond to an "enter" button. I assumed I could do this with a focused element and am trying to set up my ContentView to handle such interaction.
The ContentView class inherits from VisualElement and has a Focus() method. The documentation states that for the Focus() method to work, the "[element] must be able to receive focus."
How can I make an element "able to receive focus"? It doesn't appear to work in UWP or Tizen with this bare-bones example. Tabbing, clicking, arrow keys, programmatically setting the focus... Nothing sets IsFocused to true, and my callback is never called.
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MyProject.Components
{
public class FocusMe : ContentView
{
public FocusMe ()
{
Content = new StackLayout {
Children = {
new Label { Text = "Welcome to Xamarin.Forms!" }
}
};
Focused += FocusMe_Focused;
Task.Run(async () => {
await Task.Delay(5000);
Focus(); // false
_ = IsFocused; // false
});
}
private void FocusMe_Focused(object sender, FocusEventArgs e)
{
// never called
}
}
}
Can a ContentView receive focus?
Base on the testing, ContentView will not receive Focused event. However, Frame layout could receive focused event, and frame inherits from ContentView, you could use Frame to replace. Please note, you need to place focusable control in it such as button.
If you work on Tizen TV, How about check TV.UIControls, it is a extension library for TV applications
If do you want to react a key event on a view, I recommend to use ContentButton view on TV.UIControls
It is based on ContentView but works like a button
Here is a guide
https://samsung.github.io/Tizen.TV.UIControls/guides/ContentButton.html

Xamarin.Forms (iOS) - How to change the text of the SearchBar "Cancel" button?

We are internationalizing a mobile app my team is developing, and I noticed the "Cancel" button in the SearchBar is not translating (everything else is translating just fine) - it always says "Cancel" in English no matter what language I change the iPad simulator to. If I can set the text myself, then I can ensure it's internationalized properly. So...
How do I change the "Cancel" button text on a Xamarin.Forms SearchBar? I've tried a custom renderer, but am having difficulty targeting the Cancel button subview. This answer seems to do a great job of explaining how to do that in Objective C, but I'm having trouble translating it into C# within the Xamarin.Forms framework.
Create a new custom renderer for iOS. Something like CustomSearchBarRenderer and subclass the Xamarin's original SearchBarRenderer
public class CustomSearchBarRenderer : SearchBarRenderer { }
I'd probably try to override OnElementChanged method and set the custom title after the base class has done all the work. Like this:
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
var cancelButton = Control.FindDescendantView<UIButton>();
cancelButton.Title = "Whatever";
}
Edit: Please be aware that the cancelButton might get recreated at some point so you might have to set the title at some other point too. However, this should be a good starting point.
You can also take a look at the current SearchBarRenderer implementation here.
I combined the answers and set the button text on each event which is needed. I also fixed the showing and hiding of the cancel button (see here):
using FestivalHolledauApp.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using System.Linq;
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
namespace FestivalHolledauApp.iOS
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
// Fixing Cancel button
if (e.NewElement != null)
{
this.Control.TextChanged += (s, ea) =>
{
this.Control.ShowsCancelButton = true;
SetCancelButtonText();
};
this.Control.OnEditingStarted += (s, ea) => //when control receives focus
{
this.Control.ShowsCancelButton = true;
SetCancelButtonText();
};
this.Control.OnEditingStopped += (s, ea) => //when control looses focus
{
this.Control.ShowsCancelButton = false;
};
}
}
private void SetCancelButtonText()
{
var cancelButton = Control.Descendants().OfType<UIButton>().FirstOrDefault();
if (cancelButton != null)
{
cancelButton.SetTitle("Schließen", UIControlState.Normal);
}
}
}
}
I've come over the same issue with ...
the "Cancel" button in a UISearchBar
the "Back" button to prior pages without title
context menu actions like "Cut", "Copy" and "Paste"
the "Done" buttons on date pickers and multiline editors
Everything else I got localized with resource based localization.
There are a lot of hacks out there finding the native buttons and setting texts manually but I knew this was not the way it was meant to be. So I digged deeper and found the following excellent article from Gerald Versluis:
https://blog.verslu.is/xamarin/xamarin-forms-xamarin/localization-default-ios-controls/
For me, this just meant to add the following block to my Info.plist:
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
</array>
With this, iOS automatically translated all the UI elements for the current system culture (without adding these text to any resource files).

How can I cancel an Await in a WPF dialog when the user clicks the close button with MVVM?

I have an application that requires a user to enter SQL connection information in a dialog. When the user clicks the "Connect" button, I await the result of SqlConnection.OpenAsync() with the supplied credentials in order to test that they are valid before closing the dialog box. I also disable the Connect button to avoid duplicate attempts. When the credentials are correct, it returns almost immediately, but when they are not, it can take up to 30 seconds to return. The cancel button is able to use a CancellationToken to cancel the request and close the dialog.
The problem is that the user is still able to click the window's close button on the dialog to dismiss it. My view model doesn't get notified, but the form is still closed. 30 seconds or so later the connection attempt returns with error information, and shows a message box.
Is there a good, MVVM friendly way, to cancel the connection attempt with my CancelationToken when the form is closed in this way? I know that I could rig up something using in the dialog's code-behind, but I'd like to avoid referencing the view model from there.
Depending on what you are using for your MVVM infrastructure, you could use something like MVVM Light's EventToCommand on the Window.Closing. You may bind this event to the cancellation command that you have attached to the button.
See MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? for a way to do this.
Try awaiting the result of SqlConnection.OpenAsync() and pass in a cancellation token.
You can write an extension-method therefor.
So you have a class (let's call it WindowExtensions) where your attached property is definded.
internal class WindowExtensions
{
public static readonly DependencyProperty WindowClosingCommandProperty = DependencyProperty.RegisterAttached(
"WindowClosingCommand", typeof (ICommand), typeof (WindowExtensions), new PropertyMetadata(null, OnWindowClosingCommandChanged));
private static void OnWindowClosingCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window window = d as Window;
if (window == null)
return;
if (e.NewValue != null)
{
window.Closing += WindowOnClosing;
}
}
private static void WindowOnClosing(object sender, CancelEventArgs e)
{
Window window = sender as Window;
if (window == null)
return;
ICommand windowClosingCommand = GetWindowClosingCommand(window);
windowClosingCommand.Execute(e);
}
public static void SetWindowClosingCommand(DependencyObject element, ICommand value)
{
element.SetValue(WindowClosingCommandProperty, value);
}
public static ICommand GetWindowClosingCommand(DependencyObject element)
{
return (ICommand) element.GetValue(WindowClosingCommandProperty);
}
}
In your XAML on the Window-Element you can map your attached-property to an ICommand-Property in your ViewModel like:
nameSpaceOfWindowExtensions:WindowExtensions.WindowClosingCommand="{Binding WindowClosingCommand}"
And in your ViewModel you have a ICommand-Property where you can handle it.
Something like:
private ICommand windowClosingCommand;
public ICommand WindowClosingCommand
{
get { return windowClosingCommand ?? (windowClosingCommand = new RelayCommand(OnWindowClosing)); }
}
private void OnWindowClosing(object parameter)
{
CancelEventArgs cancelEventArgs = parameter as CancelEventArgs;
if (cancelEventArgs != null)
{
// If you want to cancel the closing of the window you can call the following:
//cancelEventArgs.Cancel = true;
}
}
If you don't need the CancelEventArgs in your ViewModel, just modify the following line in the attached-property:
windowClosingCommand.Execute(e);
to
windowClosingCommand.Execute(null);

Does data binding work on invisible control?

This is a .net problem with winforms, not asp.net.
I have a windows form with several tabs. I set data bindings of all controls when the form is loaded. But I have noticed that the data bindings of controls on the second tab do not work. Those bindings work only when the form is loaded and when I select the second tab. This brings the suspicion to me: data bindings work only when bound controls become visible.
Anyone can tell me whether this is true or not? It is not hard to test this but I would like to know some confirmation.
Thanks
You are correct. A data-bound control are not updated until the control is made visible.
The only reference I can find for this at the moment is this MSDN thread.
Your issue has to do with the behavior of the TabControl. See Microsoft bug report. I posted a workaround for that problem which subclasses the TabControl and 'Iniatalizes' all the tab pages when the control is created or the handle is created. Below is the code for the workaround.
public partial class TabControl : System.Windows.Forms.TabControl
{
protected override void OnHandleCreated(EventArgs e_)
{
base.OnHandleCreated(e_);
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, true, Created);
}
}
protected override void OnControlAdded(ControlEventArgs e_)
{
base.OnControlAdded(e_);
System.Windows.Forms.TabPage page = e_.Control as System.Windows.Forms.TabPage;
if ((page != null) && (page.Parent == this) && (IsHandleCreated || Created))
{
InitializeTabPage(page, IsHandleCreated, Created);
}
}
protected override void OnCreateControl()
{
base.OnCreateControl();
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, IsHandleCreated, true);
}
}
//PRB: Exception thrown during Windows Forms data binding if bound control is on a tab page with uncreated handle
//FIX: Make sure all tab pages are created when the tabcontrol is created.
//https://connect.microsoft.com/VisualStudio/feedback/details/351177
private void InitializeTabPage(System.Windows.Forms.TabPage page_, bool createHandle_, bool createControl_)
{
if (!createControl_ && !createHandle_)
{
return;
}
if (createHandle_ && !page_.IsHandleCreated)
{
IntPtr handle = page_.Handle;
}
if (!page_.Created && createControl_)
{
return;
}
bool visible = page_.Visible;
if (!visible)
{
page_.Visible = true;
}
page_.CreateControl();
if (!visible)
{
page_.Visible = false;
}
}
}
We've encountered a similar problem. We're trying to write to 2 bound, invisible fields so that we can change the format that we write to our dataset. This works fine when the objects are visible, but stops working when the visible property was changed to false.
To get round it, I added the following code:
// Stop our screen flickering.
chSplitContainer.Panel2.SuspendLayout();
// Make the bound fields visible or the binding doesn't work.
tbxValueCr.Visible = true;
tbxValueDb.Visible = true;
// Update the fields here.
<DO STUFF>
// Restore settings to how they were, so you don't know we're here.
tbxValueCr.Visible = false;
tbxValueDb.Visible = false;
chSplitContainer.Panel2.ResumeLayout();
I've struggled with this myself and concluded that the only workaround, besides subclassing apparently (see hjb417's answer), was to make the other tab visible. Switching to the other tab and going back to the previous immediately before the form is visible doesn't work. If you do not want to have the second tab visible, I've used the following code as a workaround:
this.tabControl.SelectedTab = this.tabPageB;
this.tabPageB.BindingContextChanged += (object sender, EventArgs e) => {
this.tabContainerMain.SelectedTab = this.tabPageA;
};
Assuming tabPageA is the visible tab, and tabPageB is the invisible one you want to initialize. This switches to pageB, and switches back once the data binding is complete. This is invisible to the user in the Form.
Still an ugly hack, but at least this works. Off course, he code gets even uglier when you have multiple tabs.
Sorry for necromancing this thread, but it is easy to force the invisible controls' databinding/handles to be ready using this method:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/190296c5-c3b1-4d67-a4a7-ad3cdc55da06/problem-with-binding-and-tabcontrol?forum=winforms
Simply, let's say if your controls are in tab page tpg_Second (or tabCtl.TabPages[1]), before you do anything with their data, call this first:
tpg_Second.Show()
This will not activate any of the tab pages, but viola, the databinding of the controls should work now.
This is not something I've come across directly. However, you might be experiencing a problem with the BindingContext. Without more details it's hard to say, but if I were you I'd set a breakpoint and make sure the controls are all bound in the same context.
Based on the answers, I made this method that works for me:
public partial class Form1: Form
{
private void Form1_Load(object sender, EventArgs e)
{
...
forceBindTabs(tabControl1);
}
private void forceBindTabs(TabControl ctl)
{
ctl.SuspendLayout();
foreach (TabPage tab in ctl.TabPages)
tab.Visible = true;
ctl.ResumeLayout();
}
}
In addition to solving the problem, the tabs are loaded at the beginning and are displayed faster when the user clicks on them.

A Possible Threading/COM/UI problem

I am writing a toolbar for IE(6+). I have used the various sample bars from
codeproject.com (http://www.codeproject.com/KB/dotnet/IE_toolbar.aspx), and have a toolbar that works, registers unregisters etc. What I want the toolbar to do is to highlight divs within an html page as the users' mouse moves over that div. So far the highlighting code works, but I would like to display the name of the div (if it exists) in a label on the toolbar (changing as the mouse moves etc).
I cannot for the life of me get this to happen and trying to debug it is a nightmare. As the assembly is hosted in IE, I suspect that I am causing an exception (in IE) by trying to update the text on the label from a thread that didn't create that control, but because that exception is happening in IE, I don't see it.
Is the solution to try to update the control in a thread-safe way using Invoke? If so how?
Here is the event code:
private void Explorer_MouseOverEvent(mshtml.IHTMLEventObj e)
{
mshtml.IHTMLDocument2 doc = this.Explorer.Document as IHTMLDocument2;
element = doc.elementFromPoint(e.clientX, e.clientY);
if (element.tagName.Equals("DIV", StringComparison.InvariantCultureIgnoreCase))
{
element.style.border = "thin solid blue;";
if (element.className != null)
{
UpdateToolstrip(element.className);
}
}
e.returnValue = false;
}
and here is an attempt at thread-safe update of the toolbar:
delegate void UpdateToolstripDelegate(string text);
public void UpdateToolstrip(string text)
{
if (this.toolStripLabel1.InvokeRequired == false)
{
this.toolStripLabel1.Text = text;
}
else
{
this.Invoke(new UpdateToolstripDelegate(UpdateToolstrip), new object[] { text });
}
}
Any suggestions much appreciated.
I can't really reproduce the issue (creating a test project for an IE toolbar is a tad too much work), but you can try this:
Add the following routine to a public static (extensions methods) class:
public static void Invoke(this Control control, MethodInvoker methodInvoker)
{
if (control.InvokeRequired)
control.Invoke(methodInvoker);
else
methodInvoker();
}
And then replace the section of similar code in the first block with this:
if (element.className != null)
{
this.Invoke(() => toolStripLabel1.Text = element.className);
}
This is a sure-fire way of avoiding thread-safe issues in UI applications.

Categories

Resources