committed by
GitHub
39 changed files with 318 additions and 244 deletions
@ -0,0 +1,20 @@ |
|||||
|
using ReactiveUI; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Windows.Input; |
||||
|
|
||||
|
namespace BindingTest.ViewModels |
||||
|
{ |
||||
|
public class NestedCommandViewModel : ReactiveObject |
||||
|
{ |
||||
|
public NestedCommandViewModel() |
||||
|
{ |
||||
|
Command = ReactiveCommand.Create(); |
||||
|
} |
||||
|
|
||||
|
public ICommand Command { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.Logging |
||||
|
{ |
||||
|
internal static class LoggerExtensions |
||||
|
{ |
||||
|
public static void LogIfError( |
||||
|
this BindingNotification notification, |
||||
|
object source, |
||||
|
AvaloniaProperty property) |
||||
|
{ |
||||
|
if (notification.ErrorType == BindingErrorType.Error) |
||||
|
{ |
||||
|
if (notification.Error is AggregateException aggregate) |
||||
|
{ |
||||
|
foreach (var inner in aggregate.InnerExceptions) |
||||
|
{ |
||||
|
LogError(source, property, inner); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
LogError(source, property, notification.Error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void LogError(object source, AvaloniaProperty property, Exception e) |
||||
|
{ |
||||
|
var level = LogEventLevel.Warning; |
||||
|
|
||||
|
if (e is BindingChainException b && |
||||
|
!string.IsNullOrEmpty(b.Expression) && |
||||
|
string.IsNullOrEmpty(b.ExpressionErrorPoint)) |
||||
|
{ |
||||
|
// The error occurred at the root of the binding chain: it's possible that the
|
||||
|
// DataContext isn't set up yet, so log at Information level instead of Warning
|
||||
|
// to prevent spewing hundreds of errors.
|
||||
|
level = LogEventLevel.Information; |
||||
|
} |
||||
|
|
||||
|
Logger.Log( |
||||
|
level, |
||||
|
LogArea.Binding, |
||||
|
source, |
||||
|
"Error in binding to {Target}.{Property}: {Message}", |
||||
|
source, |
||||
|
property, |
||||
|
e.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,23 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Avalonia.Utilities |
|
||||
{ |
|
||||
internal static class ExceptionUtilities |
|
||||
{ |
|
||||
public static string GetMessage(Exception e) |
|
||||
{ |
|
||||
var aggregate = e as AggregateException; |
|
||||
|
|
||||
if (aggregate != null) |
|
||||
{ |
|
||||
return string.Join(" | ", aggregate.InnerExceptions.Select(x => x.Message)); |
|
||||
} |
|
||||
|
|
||||
return e.Message; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,125 @@ |
|||||
|
using System; |
||||
|
using System.Windows.Input; |
||||
|
using Avalonia.Markup.Xaml.Data; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
public class ButtonTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Button_Is_Disabled_When_Command_Is_Disabled() |
||||
|
{ |
||||
|
var command = new TestCommand(false); |
||||
|
var target = new Button |
||||
|
{ |
||||
|
Command = command, |
||||
|
}; |
||||
|
|
||||
|
Assert.False(target.IsEnabled); |
||||
|
command.IsEnabled = true; |
||||
|
Assert.True(target.IsEnabled); |
||||
|
command.IsEnabled = false; |
||||
|
Assert.False(target.IsEnabled); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Button_Is_Disabled_When_Bound_Command_Doesnt_Exist() |
||||
|
{ |
||||
|
var target = new Button |
||||
|
{ |
||||
|
[!Button.CommandProperty] = new Binding("Command"), |
||||
|
}; |
||||
|
|
||||
|
Assert.False(target.IsEnabled); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Button_Is_Disabled_When_Bound_Command_Is_Removed() |
||||
|
{ |
||||
|
var viewModel = new |
||||
|
{ |
||||
|
Command = new TestCommand(true), |
||||
|
}; |
||||
|
|
||||
|
var target = new Button |
||||
|
{ |
||||
|
DataContext = viewModel, |
||||
|
[!Button.CommandProperty] = new Binding("Command"), |
||||
|
}; |
||||
|
|
||||
|
Assert.True(target.IsEnabled); |
||||
|
target.DataContext = null; |
||||
|
Assert.False(target.IsEnabled); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Button_Is_Enabled_When_Bound_Command_Is_Added() |
||||
|
{ |
||||
|
var viewModel = new |
||||
|
{ |
||||
|
Command = new TestCommand(true), |
||||
|
}; |
||||
|
|
||||
|
var target = new Button |
||||
|
{ |
||||
|
DataContext = new object(), |
||||
|
[!Button.CommandProperty] = new Binding("Command"), |
||||
|
}; |
||||
|
|
||||
|
Assert.False(target.IsEnabled); |
||||
|
target.DataContext = viewModel; |
||||
|
Assert.True(target.IsEnabled); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Button_Is_Disabled_When_Disabled_Bound_Command_Is_Added() |
||||
|
{ |
||||
|
var viewModel = new |
||||
|
{ |
||||
|
Command = new TestCommand(false), |
||||
|
}; |
||||
|
|
||||
|
var target = new Button |
||||
|
{ |
||||
|
DataContext = new object(), |
||||
|
[!Button.CommandProperty] = new Binding("Command"), |
||||
|
}; |
||||
|
|
||||
|
Assert.False(target.IsEnabled); |
||||
|
target.DataContext = viewModel; |
||||
|
Assert.False(target.IsEnabled); |
||||
|
} |
||||
|
|
||||
|
private class TestCommand : ICommand |
||||
|
{ |
||||
|
private bool _enabled; |
||||
|
|
||||
|
public TestCommand(bool enabled) |
||||
|
{ |
||||
|
_enabled = enabled; |
||||
|
} |
||||
|
|
||||
|
public bool IsEnabled |
||||
|
{ |
||||
|
get { return _enabled; } |
||||
|
set |
||||
|
{ |
||||
|
if (_enabled != value) |
||||
|
{ |
||||
|
_enabled = value; |
||||
|
CanExecuteChanged?.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public event EventHandler CanExecuteChanged; |
||||
|
|
||||
|
public bool CanExecute(object parameter) => _enabled; |
||||
|
|
||||
|
public void Execute(object parameter) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue