Skip to content

Commit

Permalink
Merge branch 'master' into 3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nigel-sampson committed Nov 30, 2015
2 parents 55b0cf2 + 88a7c60 commit 2d8aaeb
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 384 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@ Caliburn.Micro is a small, yet powerful framework, designed for building applica

## Sponsoring

This community project is sponsored by [Xceed](http://xceed.com/), makers of Xceed DataGrid for WPF. 50% off any Xceed product with coupon code G00B8T.

The Caliburn.Micro team uses ReSharper by [JetBrains](http://www.jetbrains.com/).

If you like what you find here, please consider [donating](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VZURNT9MCX3CS).
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Threading.Tasks;

namespace Caliburn.Micro.WinRT.Sample.ViewModels
{
public class ActionsViewModel : ViewModelBase
{
private string input;
private string output;
string input;
string input2;
string output;

public ActionsViewModel(INavigationService navigationService)
: base(navigationService)
Expand All @@ -24,33 +26,41 @@ public string Output
}
}

public string Input
public void SimpleSayHello()
{
get
{
return input;
}
set
{
this.Set(ref input, value);
}
Output = "Hello from Caliburn.Micro";
}

public void SimpleSayHello()
public async Task AsyncSayHelloAsync()
{
Output = "Hello from Caliburn.Micro";
await Task.Delay(0);

Output = "Hello from Caliburn.Micro (async)";
}

public void SayHello(string name)
{
Output = String.Format("Hello {0} from Caliburn.Micro", Input);
Output = String.Format("Hello {0} from Caliburn.Micro", name);
}

public bool CanSayHello(string name)
{
return !String.IsNullOrEmpty(name);
}

public async Task SayHello2Async(string name)
{
await Task.Delay(0);

Output = String.Format("Hello {0} from Caliburn.Micro (async)", name);
}

// Notice that the guard method is sync and is missing the Async suffix.
public bool CanSayHello2(string name)
{
return !String.IsNullOrEmpty(name);
}

public void AppBarHello()
{
Output = "Hello from the App Bar.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,15 @@
<TextBlock x:Name="Output" Style="{StaticResource ItemTextStyle}"/>
</StackPanel>
<Button x:Name="SimpleSayHello" Content="Simple Say Hello" Margin="-3,0,0,0" />
<StackPanel Orientation="Horizontal" Margin="0,20">
<Button x:Name="AsyncSayHello" Content="Simple Say Hello (async)" Margin="-3,0,0,0" />
<StackPanel Margin="0,10" Orientation="Horizontal">
<TextBox x:Name="Input" Width="150" />
<Button Content="Say Hello with Parameter" caliburn:Message.Attach="SayHello(Input.Text)"/>
</StackPanel>
<StackPanel Margin="0,20" Orientation="Horizontal">
<TextBox x:Name="AsyncInput" Width="150" />
<Button Content="Say Hello with Parameter (async)" caliburn:Message.Attach="SayHello2Async(AsyncInput.Text)" />
</StackPanel>
<Rectangle Fill="{StaticResource MetroOrangeBrush}" Width="100" Height="100" HorizontalAlignment="Left"
caliburn:Message.Attach="[Event DoubleTapped] = [Action SimpleSayHello]"/>
</StackPanel>
Expand Down
100 changes: 55 additions & 45 deletions src/Caliburn.Micro.Platform/ActionMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,32 +439,40 @@ public override string ToString() {
/// </summary>
public static Action<ActionExecutionContext> PrepareContext = context => {
SetMethodBinding(context);
if (context.Target == null || context.Method == null) {
if (context.Target == null || context.Method == null)
{
return;
}
var possibleGuardNames = BuildPossibleGuardNames(context).ToList();

var guardName = "Can" + context.Method.Name;
var targetType = context.Target.GetType();
var guard = TryFindGuardMethod(context);
var guard = TryFindGuardMethod(context, possibleGuardNames);

if (guard == null) {
if (guard == null)
{
var inpc = context.Target as INotifyPropertyChanged;
if (inpc == null)
return;
#if WinRT
guard = targetType.GetRuntimeMethods().SingleOrDefault(m => m.Name == "get_" + guardName);
#else
guard = targetType.GetMethod("get_" + guardName);
#endif

var targetType = context.Target.GetType();
string matchingGuardName = null;
foreach (string possibleGuardName in possibleGuardNames)
{
matchingGuardName = possibleGuardName;
guard = GetMethodInfo(targetType, "get_" + matchingGuardName);
if (guard != null) break;
}

if (guard == null)
return;

PropertyChangedEventHandler handler = null;
handler = (s, e) => {
if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == guardName) {
if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == matchingGuardName)
{
Caliburn.Micro.Execute.OnUIThread(() => {
var message = context.Message;
if (message == null) {
if (message == null)
{
inpc.PropertyChanged -= handler;
return;
}
Expand All @@ -480,24 +488,27 @@ public override string ToString() {

context.CanExecute = () => (bool)guard.Invoke(
context.Target,
MessageBinder.DetermineParameters(context, guard.GetParameters())
);
MessageBinder.DetermineParameters(context, guard.GetParameters()));
};

/// <summary>
/// Try to find a candidate for guard function, having:
/// - a name in the form "CanXXX"
/// - no generic parameters
/// - a bool return type
/// - no parameters or a set of parameters corresponding to the action method
/// Try to find a candidate for guard function, having:
/// - a name matching any of <paramref name="possibleGuardNames"/>
/// - no generic parameters
/// - a bool return type
/// - no parameters or a set of parameters corresponding to the action method
/// </summary>
/// <param name="context">The execution context</param>
/// <returns>A MethodInfo, if found; null otherwise</returns>
static MethodInfo TryFindGuardMethod(ActionExecutionContext context) {
#if WinRT
var guardName = "Can" + context.Method.Name;
/// <param name="possibleGuardNames">Method names to look for.</param>
///<returns>A MethodInfo, if found; null otherwise</returns>
static MethodInfo TryFindGuardMethod(ActionExecutionContext context, IEnumerable<string> possibleGuardNames) {
var targetType = context.Target.GetType();
var guard = targetType.GetRuntimeMethods().SingleOrDefault(m => m.Name == guardName);
MethodInfo guard = null;
foreach (string possibleGuardName in possibleGuardNames)
{
guard = GetMethodInfo(targetType, possibleGuardName);
if (guard != null) break;
}

if (guard == null) return null;
if (guard.ContainsGenericParameters) return null;
Expand All @@ -510,38 +521,37 @@ static MethodInfo TryFindGuardMethod(ActionExecutionContext context) {

var comparisons = guardPars.Zip(
context.Method.GetParameters(),
(x, y) => x.ParameterType.Equals(y.ParameterType)
(x, y) => x.ParameterType == y.ParameterType
);

if (comparisons.Any(x => !x)) {
if (comparisons.Any(x => !x))
{
return null;
}

return guard;
#else
var guardName = "Can" + context.Method.Name;
var targetType = context.Target.GetType();
var guard = targetType.GetMethod(guardName);
}

if (guard ==null) return null;
if (guard.ContainsGenericParameters) return null;
if (typeof(bool) != guard.ReturnType) return null;
static IEnumerable<string> BuildPossibleGuardNames(ActionExecutionContext context) {

var guardPars = guard.GetParameters();
var actionPars = context.Method.GetParameters();
if (guardPars.Length == 0) return guard;
if (guardPars.Length != actionPars.Length) return null;
const string GuardPrefix = "Can";

var comparisons = guardPars.Zip(
context.Method.GetParameters(),
(x, y) => x.ParameterType == y.ParameterType
);
var methodName = context.Method.Name;
yield return GuardPrefix + methodName;

if (comparisons.Any(x => !x)) {
return null;
}
const string AsyncMethodSuffix = "Async";
if (methodName.EndsWith(AsyncMethodSuffix, StringComparison.OrdinalIgnoreCase))
{
yield return GuardPrefix + methodName.Substring(0, methodName.Length - AsyncMethodSuffix.Length);
}
}

return guard;
static MethodInfo GetMethodInfo(Type t, string methodName)
{
#if WinRT
return t.GetRuntimeMethods().SingleOrDefault(m => m.Name == methodName);
#else
return t.GetMethod(methodName);
#endif
}
}
Expand Down
36 changes: 32 additions & 4 deletions src/Caliburn.Micro.Platform/NameTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@
/// Class for managing the list of rules for doing name transformation.
/// </summary>
public class NameTransformer : BindableCollection<NameTransformer.Rule> {

#if NET
private const RegexOptions options = RegexOptions.Compiled;
#else
private const RegexOptions options = RegexOptions.None;
#endif

bool useEagerRuleSelection = true;

/// <summary>
/// Flag to indicate if transformations from all matched rules are returned. Otherwise, transformations from only the first matched rule are returned.
/// </summary>
Expand Down Expand Up @@ -62,18 +69,18 @@ public IEnumerable<string> Transform(string source, Func<string, string> getRepl
var rules = this.Reverse();

foreach(var rule in rules) {
if(!string.IsNullOrEmpty(rule.GlobalFilterPattern) && !Regex.IsMatch(source, rule.GlobalFilterPattern)) {
if(!string.IsNullOrEmpty(rule.GlobalFilterPattern) && !rule.GlobalFilterPatternRegex.IsMatch(source)) {
continue;
}

if(!Regex.IsMatch(source, rule.ReplacePattern)) {
if(!rule.ReplacePatternRegex.IsMatch(source)) {
continue;
}

nameList.AddRange(
rule.ReplacementValues
.Select(getReplaceString)
.Select(repString => Regex.Replace(source, rule.ReplacePattern, repString))
.Select(repString => rule.ReplacePatternRegex.Replace(source, repString))
);

if (!useEagerRuleSelection) {
Expand All @@ -88,6 +95,9 @@ public IEnumerable<string> Transform(string source, Func<string, string> getRepl
/// A rule that describes a name transform.
///</summary>
public class Rule {
private Regex replacePatternRegex;
private Regex globalFilterPatternRegex;

/// <summary>
/// Regular expression pattern for global filtering
/// </summary>
Expand All @@ -102,6 +112,24 @@ public class Rule {
/// The list of replacement values
/// </summary>
public IEnumerable<string> ReplacementValues;

/// <summary>
/// Regular expression for global filtering
/// </summary>
public Regex GlobalFilterPatternRegex {
get {
return globalFilterPatternRegex ?? (globalFilterPatternRegex = new Regex(GlobalFilterPattern, options));
}
}

/// <summary>
/// Regular expression for replacing text
/// </summary>
public Regex ReplacePatternRegex {
get {
return replacePatternRegex ?? (replacePatternRegex = new Regex(ReplacePattern, options));
}
}
}
}
}
15 changes: 14 additions & 1 deletion src/Caliburn.Micro.Platform/ViewModelBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Caliburn.Micro
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
#if XFORMS
using UIElement = global::Xamarin.Forms.Element;
using FrameworkElement = global::Xamarin.Forms.VisualElement;
Expand All @@ -29,6 +30,8 @@ namespace Caliburn.Micro
/// Binds a view to a view model.
/// </summary>
public static class ViewModelBinder {
const string AsyncSuffix = "Async";

static readonly ILog Log = LogManager.GetLog(typeof(ViewModelBinder));

/// <summary>
Expand Down Expand Up @@ -131,7 +134,12 @@ public static bool ShouldApplyConventions(FrameworkElement view) {

foreach (var method in methods) {
var foundControl = unmatchedElements.FindName(method.Name);
if (foundControl == null) {
if (foundControl == null && IsAsyncMethod(method)) {
var methodNameWithoutAsyncSuffix = method.Name.Substring(0, method.Name.Length - AsyncSuffix.Length);
foundControl = unmatchedElements.FindName(methodNameWithoutAsyncSuffix);
}

if(foundControl == null) {
Log.Info("Action Convention Not Applied: No actionable element for {0}.", method.Name);
continue;
}
Expand Down Expand Up @@ -174,6 +182,11 @@ public static bool ShouldApplyConventions(FrameworkElement view) {
return unmatchedElements;
};

static bool IsAsyncMethod(MethodInfo method) {
return typeof(Task).IsAssignableFrom(method.ReturnType) &&
method.Name.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Allows the developer to add custom handling of named elements which were not matched by any default conventions.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion src/Caliburn.Micro.Platform/win8/AppManifestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public Windows.UI.Color BackgroundColor

private static Windows.UI.Color ToColor(string hexValue)
{
// if 'transparent' is entered in the app manifest, return Windows.UI.Colors.Transparent
// in order to prevent parsing failures
if (String.Equals(hexValue, "transparent", StringComparison.OrdinalIgnoreCase))
return Windows.UI.Colors.Transparent;

hexValue = hexValue.Replace("#", string.Empty);

// some loose validation (not bullet-proof)
Expand Down Expand Up @@ -104,4 +109,4 @@ private static Windows.UI.Color ToColor(string hexValue)
return Windows.UI.Color.FromArgb(a, r, g, b);
}
}
}
}
Loading

0 comments on commit 2d8aaeb

Please sign in to comment.