diff --git a/NZazu.Contracts/FormDefinition.cs b/NZazu.Contracts/FormDefinition.cs index 9806c72..b61907c 100644 --- a/NZazu.Contracts/FormDefinition.cs +++ b/NZazu.Contracts/FormDefinition.cs @@ -4,5 +4,6 @@ public class FormDefinition { public FieldDefinition[] Fields { get; set; } public string Layout { get; set; } + public string FocusOn { get; set; } } } diff --git a/NZazu/Extensions/FocusExtensions.cs b/NZazu/Extensions/FocusExtensions.cs new file mode 100644 index 0000000..7b77099 --- /dev/null +++ b/NZazu/Extensions/FocusExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; + +namespace NZazu.Extensions +{ + public static class FocusExtensions + { + // cf.: https://wpf.codeplex.com/workitem/13476 + public static Task DelayedFocus(this UIElement uiElement) + { + if (uiElement == null) throw new ArgumentNullException("uiElement"); + + return uiElement.Dispatcher.BeginInvoke(SetFocusAction(uiElement), DispatcherPriority.Render).Task; + } + + static Action SetFocusAction(UIElement uiElement) + { + return () => SetFocus(uiElement); + } + + public static void SetFocus(this UIElement uiElement) + { + RemoveFocus(uiElement); + + uiElement.Focusable = true; + FocusManager.SetFocusedElement(uiElement, uiElement); + uiElement.Focus(); + Keyboard.Focus(uiElement); + } + + public static void RemoveFocus(this UIElement uiElement) + { + // cf.: http://stackoverflow.com/questions/2914495/wpf-how-to-programmatically-remove-focus-from-a-textbox + //Keyboard.ClearFocus(); + + var frameWorkElement = uiElement as FrameworkElement; + if (frameWorkElement == null) return; + + // Move to a parent that can take focus + var parent = frameWorkElement.Parent as FrameworkElement; + while (parent != null && !parent.Focusable) + { + parent = parent.Parent as FrameworkElement; + } + + var scope = FocusManager.GetFocusScope(uiElement); + FocusManager.SetFocusedElement(scope, parent); + } + } +} \ No newline at end of file diff --git a/NZazu/FieldBehavior/EmptyNZazuFieldBehavior.cs b/NZazu/FieldBehavior/EmptyNZazuFieldBehavior.cs index f2359ac..bd4a21f 100644 --- a/NZazu/FieldBehavior/EmptyNZazuFieldBehavior.cs +++ b/NZazu/FieldBehavior/EmptyNZazuFieldBehavior.cs @@ -1,14 +1,14 @@ using System; -using System.Windows.Controls; namespace NZazu.FieldBehavior { internal class EmptyNZazuFieldBehavior : NZazuFieldBehavior { - public override void AttachTo(Control valueControl) + public override void AttachTo(INZazuWpfField field) { - if (valueControl == null) throw new ArgumentNullException("valueControl"); + if (field == null) throw new ArgumentNullException("field"); } + public override void Detach() { } } } \ No newline at end of file diff --git a/NZazu/FieldBehavior/EmptyNZazuFieldBehavior_Should.cs b/NZazu/FieldBehavior/EmptyNZazuFieldBehavior_Should.cs index 34cf580..eccfe1c 100644 --- a/NZazu/FieldBehavior/EmptyNZazuFieldBehavior_Should.cs +++ b/NZazu/FieldBehavior/EmptyNZazuFieldBehavior_Should.cs @@ -33,13 +33,13 @@ public void Throw_Excepion_If_No_Control_Provided() [Test] public void Do_Nothing() { - var ctrl = Substitute.For(); + var field = Substitute.For(); var sut = new EmptyNZazuFieldBehavior(); - sut.AttachTo(ctrl); + sut.AttachTo(field); sut.Detach(); - ctrl.ReceivedCalls().Should().BeEmpty(); + field.ReceivedCalls().Should().BeEmpty(); } } } \ No newline at end of file diff --git a/NZazu/FieldBehavior/NZazuFieldBehavior.cs b/NZazu/FieldBehavior/NZazuFieldBehavior.cs index 5751063..7f47db5 100644 --- a/NZazu/FieldBehavior/NZazuFieldBehavior.cs +++ b/NZazu/FieldBehavior/NZazuFieldBehavior.cs @@ -1,10 +1,8 @@ -using System.Windows.Controls; - namespace NZazu.FieldBehavior { internal abstract class NZazuFieldBehavior : INZazuWpfFieldBehavior { - public abstract void AttachTo(Control valueControl); + public abstract void AttachTo(INZazuWpfField field); public abstract void Detach(); } } \ No newline at end of file diff --git a/NZazu/FieldBehavior/NZazuFieldBehaviorFactory_Should.cs b/NZazu/FieldBehavior/NZazuFieldBehaviorFactory_Should.cs index daad97b..dbd0a4a 100644 --- a/NZazu/FieldBehavior/NZazuFieldBehaviorFactory_Should.cs +++ b/NZazu/FieldBehavior/NZazuFieldBehaviorFactory_Should.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Windows.Controls; using FluentAssertions; using NUnit.Framework; using NZazu.Contracts; @@ -40,7 +39,7 @@ public void Verify_Parameter_On_CreateFieldBehavior() [ExcludeFromCodeCoverage] private class SimpleInterfaceImplementation : INZazuWpfFieldBehavior { - public void AttachTo(Control valueControl) { } + public void AttachTo(INZazuWpfField field) { } public void Detach() { } } diff --git a/NZazu/INZazuWpfFieldBehavior.cs b/NZazu/INZazuWpfFieldBehavior.cs index 1b5d7c4..40d75b1 100644 --- a/NZazu/INZazuWpfFieldBehavior.cs +++ b/NZazu/INZazuWpfFieldBehavior.cs @@ -1,10 +1,8 @@ -using System.Windows.Controls; - -namespace NZazu +namespace NZazu { public interface INZazuWpfFieldBehavior { - void AttachTo(Control valueControl); + void AttachTo(INZazuWpfField field); void Detach(); } } \ No newline at end of file diff --git a/NZazu/INZazuWpfView.cs b/NZazu/INZazuWpfView.cs index 9e88e0e..41e1316 100644 --- a/NZazu/INZazuWpfView.cs +++ b/NZazu/INZazuWpfView.cs @@ -13,6 +13,9 @@ public interface INZazuWpfView IResolveLayout ResolveLayout { get; set; } INZazuWpfField GetField(string key); + bool TryGetField(string key, out INZazuWpfField field); + bool TrySetFocusOn(string key); + Dictionary GetFieldValues(); void ApplyChanges(); diff --git a/NZazu/NZazu.csproj b/NZazu/NZazu.csproj index a2d6d41..86b8641 100644 --- a/NZazu/NZazu.csproj +++ b/NZazu/NZazu.csproj @@ -57,6 +57,7 @@ + diff --git a/NZazu/NZazuView.cs b/NZazu/NZazuView.cs index 22ea07a..6f0b5d9 100644 --- a/NZazu/NZazuView.cs +++ b/NZazu/NZazuView.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using NZazu.Contracts; using NZazu.Contracts.Checks; using NZazu.Extensions; @@ -30,6 +32,7 @@ private static void FormDefinitionChanged(DependencyObject d, DependencyProperty var view = (NZazuView)d; var formDefinition = (FormDefinition)e.NewValue; view.UpdateFields(formDefinition, view.FieldFactory, view.FieldBehaviorFactory, view.ResolveLayout); + view.TrySetFocusOn(formDefinition.FocusOn); } // ############# FieldFactory @@ -151,10 +154,15 @@ public void ApplyChanges() public INZazuWpfField GetField(string key) { INZazuWpfField field; - if (!_fields.TryGetValue(key, out field) - && !_groupFields.TryGetValue(key, out field)) - throw new KeyNotFoundException(); - return field; + if (TryGetField(key, out field)) + return field; + throw new KeyNotFoundException(); + } + + public bool TryGetField(string key, out INZazuWpfField field) + { + return _fields.TryGetValue(key, out field) + || _groupFields.TryGetValue(key, out field); } public Dictionary GetFieldValues() @@ -195,6 +203,20 @@ private void UpdateFields( this.SetFieldValues(FormData.Values); } + public bool TrySetFocusOn(string focusOn) + { + INZazuWpfField field; + if (String.IsNullOrWhiteSpace(focusOn) || !TryGetField(focusOn, out field)) return false; + + var control = field.ValueControl; + if (control == null) return false; + + control.SetFocus(); + control.DelayedFocus(); + + return true; + } + private void CreateFields(FormDefinition formDefinition, INZazuWpfFieldFactory fieldFactory) { formDefinition.Fields.ToList().ForEach(f => @@ -232,7 +254,7 @@ private void AttachBehavior(FormDefinition formDefinition, INZazuWpfFieldBehavio var field = GetField(fieldDefinition.Key) as NZazuField; if (field == null) return; - behavior.AttachTo(field.ValueControl); + behavior.AttachTo(field); field.Behavior = behavior; } } diff --git a/NZazu/NZazuView_Should.cs b/NZazu/NZazuView_Should.cs index c18e2f1..2bcaf72 100644 --- a/NZazu/NZazuView_Should.cs +++ b/NZazu/NZazuView_Should.cs @@ -341,7 +341,7 @@ public void Attach_And_Detach_Behavior_To_Field() var sut = new NZazuView { FieldBehaviorFactory = behaviorFactory, FormDefinition = formDefinition, FormData = formData }; sut.Should().NotBeNull(); - behavior.ReceivedWithAnyArgs().AttachTo(Arg.Any()); + behavior.ReceivedWithAnyArgs().AttachTo(Arg.Any()); behavior.ClearReceivedCalls(); // now lets create a ner form and detach the existing behavior @@ -371,5 +371,33 @@ public void Skip_skip_fixed_fields_in_GetFieldValues() var actual = view.GetFieldValues(); actual.ShouldBeEquivalentTo(expected); } + + [Test] + public void Focus_specified_field_after_changing_FormDefinition() + { + var view = new NZazuView + { + FormDefinition = new FormDefinition + { + Fields = new[] + { + new FieldDefinition {Key = "other", Type = "string"}, + new FieldDefinition {Key = "focus", Type = "string"} + }, + FocusOn = "focus" + } + }; + + var otherCtrl = view.GetField("other").ValueControl; + var keyCtrl = view.GetField("focus").ValueControl; + + otherCtrl.IsFocused.Should().BeFalse(); + keyCtrl.IsFocused.Should().BeTrue(); + + view.TrySetFocusOn("other").Should().BeTrue(); + + keyCtrl.IsFocused.Should().BeFalse(); + otherCtrl.IsFocused.Should().BeTrue(); + } } } diff --git a/NZazuFiddle/NZazuFiddle.csproj b/NZazuFiddle/NZazuFiddle.csproj index cab5288..a521592 100644 --- a/NZazuFiddle/NZazuFiddle.csproj +++ b/NZazuFiddle/NZazuFiddle.csproj @@ -139,6 +139,7 @@ + diff --git a/NZazuFiddle/PreviewViewModel.cs b/NZazuFiddle/PreviewViewModel.cs index eb66281..a8c3e84 100644 --- a/NZazuFiddle/PreviewViewModel.cs +++ b/NZazuFiddle/PreviewViewModel.cs @@ -62,6 +62,16 @@ private set } } + protected override void OnActivate() + { + base.OnActivate(); + + var view = GetView() as PreviewView; + if (view == null) return; + + view.View.TrySetFocusOn(Definition.FocusOn); + } + public void Handle(FormDefinition definition) { Definition = definition; diff --git a/NZazuFiddle/Samples/BehaviorSample.cs b/NZazuFiddle/Samples/BehaviorSample.cs index 1f383bb..81946bc 100644 --- a/NZazuFiddle/Samples/BehaviorSample.cs +++ b/NZazuFiddle/Samples/BehaviorSample.cs @@ -63,9 +63,10 @@ private class OpenUrlOnStringEnterBehavior : INZazuWpfFieldBehavior private Control _control; private KeyEventHandler _handler; - public void AttachTo(Control valueControl) + public void AttachTo(INZazuWpfField field) { - if (valueControl == null) throw new ArgumentNullException("valueControl"); + if (field == null) throw new ArgumentNullException("field"); + var valueControl = field.ValueControl; _control = valueControl; diff --git a/NZazuFiddle/Samples/FocusSample.cs b/NZazuFiddle/Samples/FocusSample.cs new file mode 100644 index 0000000..98fb6ae --- /dev/null +++ b/NZazuFiddle/Samples/FocusSample.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using NZazu.Contracts; + +namespace NZazuFiddle.Samples +{ + // ReSharper disable once UnusedMember.Global + class FocusSample : SampleBase + { + public FocusSample() : base(50) + { + Sample = new SampleViewModel + { + Name = "Focus", + Fiddle = ToFiddle(new FormDefinition + { + Fields = new [] + { + new FieldDefinition { Key="label", Type = "label", Prompt = "Focus should be on 'value2'"}, + new FieldDefinition { Key="key1", Type="string", Prompt="key1"}, + new FieldDefinition { Key="key2", Type="string", Prompt="key2"} + }, + FocusOn = "key2" + }, + new Dictionary + { + {"key1", "value1"}, + {"key2", "value2"} + }) + }; + + } + } +} \ No newline at end of file diff --git a/NZazuFiddle/Samples/PrimitivesSample.cs b/NZazuFiddle/Samples/PrimitivesSample.cs index b6f758a..7776593 100644 --- a/NZazuFiddle/Samples/PrimitivesSample.cs +++ b/NZazuFiddle/Samples/PrimitivesSample.cs @@ -3,6 +3,7 @@ namespace NZazuFiddle.Samples { + // ReSharper disable once UnusedMember.Global class PrimitivesSample : SampleBase { public PrimitivesSample() : base(10) diff --git a/NZazuFiddle/Samples/ValidationSample.cs b/NZazuFiddle/Samples/ValidationSample.cs index 23fa0e0..96461e3 100644 --- a/NZazuFiddle/Samples/ValidationSample.cs +++ b/NZazuFiddle/Samples/ValidationSample.cs @@ -3,6 +3,7 @@ namespace NZazuFiddle.Samples { + // ReSharper disable once UnusedMember.Global class ValidationSample : SampleBase { public ValidationSample() : base(30) diff --git a/NZazuFiddle/ShellViewModel.cs b/NZazuFiddle/ShellViewModel.cs index e1c65ca..64a1647 100644 --- a/NZazuFiddle/ShellViewModel.cs +++ b/NZazuFiddle/ShellViewModel.cs @@ -24,7 +24,7 @@ public IEnumerable Samples { if (Equals(value, _samples)) return; _samples.Clear(); - if (value != null) _samples.AddRange(value.OrderBy(s => s.Name)); + if (value != null) _samples.AddRange(value); SelectedSample = _samples.FirstOrDefault(); } }