Browse Source
Buttons are disabled when there is a null in the binding chain for Commandpull/1138/head
committed by
GitHub
21 changed files with 292 additions and 94 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