├── .gitignore ├── BindingSharp.Sample ├── BindingSharp.Sample.csproj ├── Command.cs ├── Program.cs └── View │ ├── View.cs │ ├── View.glade │ ├── ViewModel.INotifyDataErrorInfo.cs │ ├── ViewModel.INotifyPropertyChanged.cs │ └── ViewModel.cs ├── BindingSharp.Test ├── BindingSharp.Test.csproj ├── Core │ ├── BindToPropertyTest.cs │ └── IBinderTest.cs ├── Gtk │ ├── BindStyleContextToNotifyDataErrorInfoTest.cs │ ├── BindToCommandTest.cs │ └── WidgetExtensionTest.cs └── TestData │ ├── TestButton.cs │ ├── TestCommand.cs │ ├── TestView.cs │ ├── TestViewModel.cs │ ├── View.cs │ └── ViewModel.cs ├── BindingSharp ├── BindingSharp.csproj ├── Core │ ├── BindINotifyPropertyChanged.IDisposable.cs │ ├── BindINotifyPropertyChanged.cs │ ├── BindingException.cs │ └── IBinder.cs ├── Gtk │ ├── BindButtonToCommand.IDisposable.cs │ ├── BindButtonToCommand.cs │ ├── BindStyleContextToNotifyDataErrorInfo.IDisposable.cs │ ├── BindStyleContextToNotifyDataErrorInfo.cs │ ├── CommandBindingAttribute.cs │ ├── PropertyBindingAttribute.cs │ ├── ValidationBindingAttribute.cs │ └── WidgetExtension.cs └── Resources │ └── invalid.css ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Dd]ebug/ 3 | [Rr]elease/ 4 | [Bb]in/ 5 | [Oo]bj/ 6 | 7 | *.nupkg 8 | **/packages/* 9 | 10 | #.vscode/ 11 | global.json 12 | coverage.* -------------------------------------------------------------------------------- /BindingSharp.Sample/BindingSharp.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | netcoreapp3.0 6 | 7 | 8 | 9 | 10 | 11 | ../../GtkSharp/BuildOutput/Debug/GtkSharp.dll 12 | 13 | 14 | ../../GtkSharp/BuildOutput/Debug/GLibSharp.dll 15 | 16 | 17 | ../../GtkSharp/BuildOutput/Debug/AtkSharp.dll 18 | 19 | 20 | ../../GtkSharp/BuildOutput/Debug/GioSharp.dll 21 | 22 | 23 | ../../GtkSharp/BuildOutput/Debug/PangoSharp.dll 24 | 25 | 26 | ../../GtkSharp/BuildOutput/Debug/GdkSharp.dll 27 | 28 | 29 | ../../GtkSharp/BuildOutput/Debug/CairoSharp.dll 30 | 31 | 32 | 33 | 34 | 35 | 36 | %(Filename)%(Extension) 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /BindingSharp.Sample/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Binding.Samples 5 | { 6 | public class Command : ICommand 7 | { 8 | public event EventHandler CanExecuteChanged; 9 | 10 | private readonly Action execute; 11 | private bool canExecute; 12 | 13 | public Command(Action execute) 14 | { 15 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 16 | canExecute = true; 17 | } 18 | 19 | public bool CanExecute(object parameter) 20 | { 21 | return canExecute; 22 | } 23 | 24 | public void SetCanExecute(bool value) 25 | { 26 | if(value != canExecute) 27 | { 28 | canExecute = value; 29 | OnCanExecuteChanged(); 30 | } 31 | } 32 | 33 | public void Execute(object parameter) 34 | { 35 | execute(parameter); 36 | } 37 | 38 | protected void OnCanExecuteChanged() 39 | { 40 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /BindingSharp.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gtk; 3 | 4 | namespace Binding.Samples 5 | { 6 | class Program 7 | { 8 | [STAThread] 9 | public static void Main(string[] args) 10 | { 11 | Application.Init(); 12 | 13 | var app = new Application("org.GTK.GTK", GLib.ApplicationFlags.None); 14 | app.Register(GLib.Cancellable.Current); 15 | 16 | var win = new Window("Sample app"); 17 | win.DeleteEvent += (o, argss) => Application.Quit(); 18 | app.AddWindow(win); 19 | 20 | var viewModel = new ViewModel(); 21 | var view = new View(viewModel); 22 | 23 | win.Add(view); 24 | win.ShowAll(); 25 | Application.Run(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BindingSharp.Sample/View/View.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using GLib; 5 | using GtkLib = Gtk; 6 | using Binding.Gtk; 7 | using UI = Gtk.Builder.ObjectAttribute; 8 | 9 | namespace Binding.Samples 10 | { 11 | public class View : GtkLib.Box 12 | { 13 | [UI] 14 | [PropertyBinding(nameof(GtkLib.Label.LabelProp), nameof(ViewModel.Label))] 15 | public GtkLib.Label Label; 16 | 17 | [UI] 18 | [CommandBinding(nameof(ViewModel.ChangeLabelCommand))] 19 | public GtkLib.Button ChangeLabelButton; 20 | 21 | [UI] 22 | [ValidationBinding(nameof(ViewModel.ToggleErrorCommand))] 23 | [CommandBinding(nameof(ViewModel.ToggleErrorCommand))] 24 | public GtkLib.Button ToggleErrorButton; 25 | 26 | public View(object viewModel) : this(new GtkLib.Builder("View.glade")) 27 | { 28 | this.Bind(viewModel); 29 | } 30 | 31 | private View(GtkLib.Builder builder) : base(builder.GetObject("View").Handle) 32 | { 33 | builder.Autoconnect(this); 34 | } 35 | 36 | protected override void Dispose(bool disposing) 37 | { 38 | if(disposing) 39 | { 40 | this.DisposeBindings(); 41 | } 42 | base.Dispose(disposing); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /BindingSharp.Sample/View/View.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | vertical 8 | 9 | 10 | Change label 11 | True 12 | True 13 | True 14 | 15 | 16 | False 17 | True 18 | 0 19 | 20 | 21 | 22 | 23 | Toggle error 24 | True 25 | True 26 | True 27 | 28 | 29 | False 30 | True 31 | 1 32 | 33 | 34 | 35 | 36 | True 37 | False 38 | Text 39 | 40 | 41 | False 42 | True 43 | 2 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BindingSharp.Sample/View/ViewModel.INotifyDataErrorInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using System.Windows.Input; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Binding.Samples 9 | { 10 | public partial class ViewModel : INotifyDataErrorInfo 11 | { 12 | public event EventHandler ErrorsChanged; 13 | 14 | private List errors = new List(); 15 | 16 | public bool HasErrors{ get; private set;} 17 | 18 | private void ToggleError(string property, string error) 19 | { 20 | if(HasErrors) 21 | { 22 | errors.Clear(); 23 | } 24 | else 25 | { 26 | errors.Add(error); 27 | } 28 | 29 | HasErrors = !HasErrors; 30 | 31 | OnErrorsChanged(property); 32 | } 33 | 34 | public IEnumerable GetErrors(string propertyName) 35 | { 36 | return errors; 37 | } 38 | 39 | protected void OnErrorsChanged([CallerMemberName] string propertyName = null) 40 | { 41 | ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /BindingSharp.Sample/View/ViewModel.INotifyPropertyChanged.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using System.Windows.Input; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Binding.Samples 9 | { 10 | public partial class ViewModel : INotifyPropertyChanged 11 | { 12 | public event PropertyChangedEventHandler PropertyChanged; 13 | 14 | protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 15 | { 16 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /BindingSharp.Sample/View/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using System.Windows.Input; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Binding.Samples 9 | { 10 | public partial class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo 11 | { 12 | public Command ChangeLabelCommand { get; } 13 | 14 | public Command ToggleErrorCommand { get; } 15 | 16 | private string label; 17 | public string Label 18 | { 19 | get { return label; } 20 | set 21 | { 22 | if(label != value) 23 | { 24 | label = value; 25 | OnPropertyChanged(); 26 | } 27 | } 28 | } 29 | 30 | public ViewModel() 31 | { 32 | ChangeLabelCommand = new Command((o) => ChangeLabelText()); 33 | ToggleErrorCommand = new Command((o) => ToggleError()); 34 | } 35 | 36 | private void ToggleError() 37 | { 38 | ToggleError(nameof(ToggleErrorCommand), "Error"); 39 | } 40 | 41 | private void ChangeLabelText() 42 | { 43 | Label = "New label text"; 44 | ChangeLabelCommand.SetCanExecute(false); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /BindingSharp.Test/BindingSharp.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | ../../GtkSharp/BuildOutput/Debug/GtkSharp.dll 13 | 14 | 15 | ../../GtkSharp/BuildOutput/Debug/GLibSharp.dll 16 | 17 | 18 | 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /BindingSharp.Test/Core/BindToPropertyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using Binding.Core; 6 | using Binding.Test.TestData; 7 | 8 | namespace Binding.Test.Core 9 | { 10 | [TestClass] 11 | public class BindTwoINotifyPropertyChangedObjectss : IBinderTest 12 | { 13 | internal override IBinder GetObject() 14 | { 15 | return new BindINotifyPropertyChanged(Mock.Of(), nameof(TestData.View.WidgetWithObjectPropery.ObjectProperty)); 16 | } 17 | 18 | [TestMethod] 19 | public void CreateWithoutSourceThrowsArgumentNullException() 20 | { 21 | Assert.ThrowsException(() => new BindINotifyPropertyChanged(null, "")); 22 | } 23 | 24 | [TestMethod] 25 | public void CreateWithoutPropertyThrowsArgumentNullException() 26 | { 27 | Assert.ThrowsException(() => new BindINotifyPropertyChanged(Mock.Of(), null)); 28 | } 29 | 30 | [TestMethod] 31 | public void CreateWithUnknownPropertyThrowsBindingException() 32 | { 33 | var view = Mock.Of(); 34 | 35 | Assert.ThrowsException(() => new BindINotifyPropertyChanged(view, "Wrong")); 36 | } 37 | 38 | [TestMethod] 39 | public void ForwardsChangedPropertyFromSourceToTarget() 40 | { 41 | object newValue = "1"; 42 | var target = new Mock(); 43 | var source = new Mock(); 44 | source.Setup(x => x.ObjectProperty).Returns(newValue); 45 | 46 | var obj = new BindINotifyPropertyChanged(source.Object, nameof(source.Object.ObjectProperty)); 47 | obj.Bind(target.Object, nameof(target.Object.ObjectProperty)); 48 | 49 | source.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(nameof(source.Object.ObjectProperty))); 50 | 51 | target.VerifySet(x => x.ObjectProperty = newValue); 52 | } 53 | 54 | [TestMethod] 55 | public void ForwardsChangedPropertyFromTargetToSource() 56 | { 57 | object newValue = "1"; 58 | var target = new Mock(); 59 | target.Setup(x => x.ObjectProperty).Returns(newValue); 60 | var source = new Mock(); 61 | 62 | var obj = new BindINotifyPropertyChanged(source.Object, nameof(source.Object.ObjectProperty)); 63 | obj.Bind(target.Object, nameof(target.Object.ObjectProperty)); 64 | 65 | target.Raise(x => x.PropertyChanged += null, new PropertyChangedEventArgs(nameof(target.Object.ObjectProperty))); 66 | 67 | source.VerifySet(x => x.ObjectProperty = newValue); 68 | } 69 | 70 | [TestMethod] 71 | public void DisposeDeregistersPropertyChangedEventFromSource() 72 | { 73 | var source = new TestWidget(); 74 | var obj = new BindINotifyPropertyChanged(source, nameof(source.TestBool)); 75 | 76 | obj.Dispose(); 77 | 78 | Assert.IsTrue(source.PropertyChangedEventRemoved); 79 | } 80 | 81 | [TestMethod] 82 | public void DisposeDeregistersPropertyChangedEventFromTarget() 83 | { 84 | var source = new TestWidget(); 85 | var target = new TestViewModel(); 86 | var obj = new BindINotifyPropertyChanged(source, nameof(source.TestBool)); 87 | obj.Bind(target, nameof(target.TestBool)); 88 | 89 | obj.Dispose(); 90 | 91 | Assert.IsTrue(target.PropertyChangedEventRemoved); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /BindingSharp.Test/Core/IBinderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Binding.Core; 4 | 5 | namespace Binding.Test.Core 6 | { 7 | [TestClass] 8 | public abstract class IBinderTest 9 | { 10 | internal abstract IBinder GetObject(); 11 | 12 | [TestMethod] 13 | public void BindThrowsArgumentNullExceptionIfTargetIsNull() 14 | { 15 | var obj = GetObject(); 16 | Assert.ThrowsException(() => obj.Bind(null, "")); 17 | } 18 | 19 | [TestMethod] 20 | public void BindThrowsBindingExceptionIfAttributeIsNotFoundInTarget() 21 | { 22 | var obj = GetObject(); 23 | 24 | Assert.ThrowsException(() => obj.Bind(new object(), "Invalid")); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /BindingSharp.Test/Gtk/BindStyleContextToNotifyDataErrorInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Collections.Generic; 4 | using Gtk; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Moq; 7 | using Binding.Gtk; 8 | using Binding.Test.TestData; 9 | 10 | namespace Binding.Test.Gtk 11 | { 12 | [TestClass] 13 | public class BindStyleContextToNotifyDataErrorInfoTest 14 | { 15 | internal BindStyleContextToNotifyDataErrorInfo GetObject(IStyleContext styleContext = null, string cssClassName = null) 16 | { 17 | if(styleContext == null) 18 | styleContext = Mock.Of(); 19 | 20 | if(cssClassName == null) 21 | cssClassName = ""; 22 | 23 | return new BindStyleContextToNotifyDataErrorInfo(styleContext, cssClassName); 24 | } 25 | [TestMethod] 26 | public void CreateWithoutStyleContextThrowsArgumentNullException() 27 | { 28 | Assert.ThrowsException(() => new BindStyleContextToNotifyDataErrorInfo(null, "")); 29 | } 30 | 31 | [TestMethod] 32 | public void CreateWithoutPropertyThrowsArgumentNullException() 33 | { 34 | Assert.ThrowsException(() => new BindStyleContextToNotifyDataErrorInfo(Mock.Of(), null)); 35 | } 36 | 37 | [TestMethod] 38 | public void BindWithoutTargetThrowsArgumentNullException() 39 | { 40 | var obj = GetObject(); 41 | Assert.ThrowsException(() => obj.Bind(null, "")); 42 | } 43 | 44 | [TestMethod] 45 | public void BindWithTargetWhichIsNoINotifyDataErrorInfoThrowsArgumentException() 46 | { 47 | var obj = GetObject(); 48 | Assert.ThrowsException(() => obj.Bind(new object(), "")); 49 | } 50 | 51 | [TestMethod] 52 | public void GenericBindWithoutTargetThrowsArgumentNullException() 53 | { 54 | var obj = GetObject(); 55 | Assert.ThrowsException(() => obj.Bind(default(INotifyDataErrorInfo), "")); 56 | } 57 | 58 | [TestMethod] 59 | public void GenericBindWithoutPropertyThrowsArgumentNullException() 60 | { 61 | var obj = GetObject(); 62 | Assert.ThrowsException(() => obj.Bind(Mock.Of(), null)); 63 | } 64 | 65 | [TestMethod] 66 | public void OnErrorsChangedAddsClassToStyleContextIfNotPresent() 67 | { 68 | var cssClasName = "cssClassName"; 69 | var property = "para"; 70 | 71 | var styleContext = new Mock(); 72 | styleContext.Setup(x => x.HasClass(cssClasName)).Returns(false); 73 | 74 | var notifyDataErrorInfo = new Mock(); 75 | notifyDataErrorInfo.Setup(x => x.GetErrors(property)).Returns(new List(){"Error"}); 76 | 77 | var obj = GetObject(styleContext.Object, cssClasName); 78 | obj.Bind(notifyDataErrorInfo.Object, property); 79 | 80 | notifyDataErrorInfo.Raise(x => x.ErrorsChanged += null, new DataErrorsChangedEventArgs(property)); 81 | 82 | styleContext.Verify(x => x.AddClass(cssClasName)); 83 | } 84 | 85 | [TestMethod] 86 | public void OnErrorsChangedRemovesClassFromStyleContextIfPresent() 87 | { 88 | var cssClasName = "cssClassName"; 89 | var property = "para"; 90 | 91 | var styleContext = new Mock(); 92 | styleContext.Setup(x => x.HasClass(cssClasName)).Returns(true); 93 | 94 | var notifyDataErrorInfo = new Mock(); 95 | notifyDataErrorInfo.Setup(x => x.GetErrors(property)).Returns(new List()); 96 | 97 | var obj = GetObject(styleContext.Object, cssClasName); 98 | obj.Bind(notifyDataErrorInfo.Object, property); 99 | 100 | notifyDataErrorInfo.Raise(x => x.ErrorsChanged += null, new DataErrorsChangedEventArgs(property)); 101 | 102 | styleContext.Verify(x => x.RemoveClass(cssClasName)); 103 | } 104 | 105 | [TestMethod] 106 | public void DisposeDeregistersErrorsChangedEvent() 107 | { 108 | var viewModel = new TestViewModel(); 109 | var obj = GetObject(); 110 | obj.Bind(viewModel, ""); 111 | 112 | obj.Dispose(); 113 | 114 | Assert.IsTrue(viewModel.ErrorsChangedEventRemoved); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /BindingSharp.Test/Gtk/BindToCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Gtk; 4 | using Moq; 5 | using Binding.Test.TestData; 6 | using System.Windows.Input; 7 | using Binding.Gtk; 8 | using Binding.Core; 9 | using Binding.Test.Core; 10 | 11 | namespace Binding.Test.Gtk 12 | { 13 | [TestClass] 14 | public class BindToCommandTest : IBinderTest 15 | { 16 | 17 | internal override IBinder GetObject() 18 | { 19 | return new BindButtonToCommand(Mock.Of()); 20 | } 21 | 22 | [TestMethod] 23 | public void CreateWithoutGtkButtonThrowsArgumentNullException() 24 | { 25 | Assert.ThrowsException(()=> new BindButtonToCommand(null)); 26 | } 27 | 28 | [TestMethod] 29 | public void BindThrowsBindingExceptionIfAttributeDoesNotReferenceICommandInViewModel() 30 | { 31 | var viewModel = new Mock(); 32 | viewModel.Setup(x => x.ObjectProperty).Returns(new object()); 33 | 34 | var obj = new BindButtonToCommand(Mock.Of()); 35 | 36 | Assert.ThrowsException(() => obj.Bind(viewModel.Object, nameof(TestData.ViewModel.WithCommandProperty.ObjectProperty))); 37 | } 38 | 39 | [DataTestMethod] 40 | [DataRow(true)] 41 | [DataRow(false)] 42 | public void ForwardsICommandCanExecuteChangedEventToReferencedGtkButtonFieldIsSensitiveProperty(bool canExecute) 43 | { 44 | var returnFirst = !canExecute; 45 | var returnLast = canExecute; 46 | 47 | var button = new Mock(); 48 | var command = new Mock(); 49 | command.Setup(x => x.CanExecute(It.IsAny())).Returns(returnFirst); 50 | 51 | var viewModel = new Mock(); 52 | viewModel.Setup(x => x.CommandProperty).Returns(command.Object); 53 | 54 | var obj = new BindButtonToCommand(button.Object); 55 | obj.Bind(viewModel.Object,nameof(viewModel.Object.CommandProperty)); 56 | 57 | //Setup again, to return a different value, if event is raised 58 | command.Setup(x => x.CanExecute(It.IsAny())).Returns(returnLast); 59 | command.Raise(x => x.CanExecuteChanged += null, EventArgs.Empty); 60 | 61 | command.Verify(x => x.CanExecute(It.IsAny()), Times.AtLeastOnce); 62 | button.VerifySet(x => x.Sensitive = returnLast, Times.Once); 63 | } 64 | 65 | [DataTestMethod] 66 | [DataRow(true)] 67 | [DataRow(false)] 68 | public void BindSetsGtkButtonSensitiveToICommandCanExecuteMethod(bool canExecute) 69 | { 70 | var button = new Mock(); 71 | var command = new Mock(); 72 | command.Setup(x => x.CanExecute(It.IsAny())).Returns(canExecute); 73 | 74 | var viewModel = new Mock(); 75 | viewModel.Setup(x => x.CommandProperty).Returns(command.Object); 76 | 77 | var obj = new Binding.Gtk.BindButtonToCommand(button.Object); 78 | obj.Bind(viewModel.Object, nameof(viewModel.Object.CommandProperty)); 79 | 80 | command.Verify(x => x.CanExecute(It.IsAny()), Times.Once); 81 | button.VerifySet(b => b.Sensitive = canExecute); 82 | } 83 | 84 | [TestMethod] 85 | public void ForwardGtkButtonFieldClickedEventToICommandExecuteMethod() 86 | { 87 | var button = new Mock(); 88 | var command = new Mock(); 89 | command.Setup(x => x.CanExecute(It.IsAny())).Returns(true); 90 | 91 | var viewModel = new Mock(); 92 | viewModel.Setup(x => x.CommandProperty).Returns(command.Object); 93 | 94 | var obj = new Binding.Gtk.BindButtonToCommand(button.Object); 95 | obj.Bind(viewModel.Object, nameof(TestData.ViewModel.WithCommandProperty.CommandProperty)); 96 | 97 | command.Verify(x => x.Execute(It.IsAny()), Times.Never); 98 | button.Raise( x => x.Clicked += null, EventArgs.Empty); 99 | command.Verify(x => x.Execute(It.IsAny()), Times.Once); 100 | } 101 | 102 | [TestMethod] 103 | public void DisposeDeregistersButtonClickedEvent() 104 | { 105 | var button = new TestButton(); 106 | var obj = new Binding.Gtk.BindButtonToCommand(button); 107 | 108 | obj.Dispose(); 109 | 110 | Assert.IsTrue(button.ClickedEventWasRemoved); 111 | } 112 | 113 | [TestMethod] 114 | public void DisposeDeregistersCommandCanExecuteChanged() 115 | { 116 | var command = new TestCommand(); 117 | var viewModel = new Mock(); 118 | viewModel.Setup(x => x.CommandProperty).Returns(command); 119 | 120 | var obj = new Binding.Gtk.BindButtonToCommand(Mock.Of()); 121 | obj.Bind(viewModel.Object, nameof(TestData.ViewModel.WithCommandProperty.CommandProperty)); 122 | 123 | obj.Dispose(); 124 | 125 | Assert.IsTrue(command.CanExecuteChangedWasRemoved); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /BindingSharp.Test/Gtk/WidgetExtensionTest.cs: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | using Binding.Core; 5 | using Binding.Gtk; 6 | using Binding.Test.TestData; 7 | 8 | namespace Binding.Test.Gtk 9 | { 10 | [TestClass] 11 | public class WidgetExtensionTest 12 | { 13 | [TestMethod] 14 | public void BindViewModelIgnoresPropertiesWithoutCommandBindingAttribute() 15 | { 16 | var viewModel = new Mock(); 17 | var view = new View.WithoutCommandBinding(); 18 | 19 | view.Bind(viewModel.Object); 20 | 21 | viewModel.VerifyNoOtherCalls(); 22 | } 23 | 24 | [TestMethod] 25 | public void BindViewModelBindsButtonToICommandOfViewModel() 26 | { 27 | var viewModel = new Mock(); 28 | var button = new Mock(); 29 | var bindToCommand = new Mock(); 30 | 31 | var view = new View.WithCommandBinding(); 32 | view.Button = button.Object; 33 | 34 | var buttonPassed = false; 35 | Binding.Gtk.WidgetExtension.CommandBindingProvider = (IButton b) => 36 | { 37 | if(b == button.Object) 38 | buttonPassed = true; 39 | 40 | return bindToCommand.Object; 41 | }; 42 | 43 | view.Bind(viewModel.Object); 44 | 45 | Assert.IsTrue(buttonPassed); 46 | bindToCommand.Verify(x => x.Bind(viewModel.Object, nameof(viewModel.Object.CommandProperty))); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/TestButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Gtk; 4 | 5 | namespace Binding.Test.TestData 6 | { 7 | public class TestButton : IButton 8 | { 9 | public event PropertyChangedEventHandler PropertyChanged {add {} remove {}} 10 | 11 | public event EventHandler Clicked 12 | { 13 | add { ClickedEventWasAdded = true; } 14 | remove { ClickedEventWasRemoved = true; } 15 | } 16 | 17 | public bool ClickedEventWasRemoved; 18 | public bool ClickedEventWasAdded; 19 | 20 | public bool Sensitive { get; set;} 21 | 22 | public IStyleContext StyleContext => null; 23 | } 24 | } -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/TestCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Binding.Test.TestData 5 | { 6 | public class TestCommand : ICommand 7 | { 8 | public event EventHandler CanExecuteChanged 9 | { 10 | add { CanExecuteChangedWasAdded = true; } 11 | remove { CanExecuteChangedWasRemoved = true; } 12 | } 13 | 14 | public bool CanExecuteChangedWasAdded; 15 | public bool CanExecuteChangedWasRemoved; 16 | 17 | public bool CanExecute(object parameter) 18 | { 19 | return true; 20 | } 21 | 22 | public void Execute(object parameter) 23 | { 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/TestView.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Gtk; 3 | 4 | namespace Binding.Test.TestData 5 | { 6 | public class TestWidget : IWidget 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged 9 | { 10 | add { PropertyChangedEventAdded = true; } 11 | remove { PropertyChangedEventRemoved = true; } 12 | } 13 | 14 | public bool PropertyChangedEventAdded; 15 | public bool PropertyChangedEventRemoved; 16 | 17 | public bool TestBool { get; set; } 18 | public bool Sensitive { get; set; } 19 | 20 | public IStyleContext StyleContext => null; 21 | } 22 | } -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/TestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | 5 | namespace Binding.Test.TestData 6 | { 7 | public class TestViewModel : INotifyPropertyChanged, INotifyDataErrorInfo 8 | { 9 | public event EventHandler ErrorsChanged 10 | { 11 | add { ErrorsChangedEventAdded = true; } 12 | remove { ErrorsChangedEventRemoved = true; } 13 | } 14 | public bool ErrorsChangedEventAdded; 15 | public bool ErrorsChangedEventRemoved; 16 | 17 | public bool HasErrors => false; 18 | 19 | public IEnumerable GetErrors(string propertyName) 20 | { 21 | return default; 22 | } 23 | 24 | public event PropertyChangedEventHandler PropertyChanged 25 | { 26 | add { PropertyChangedEventAdded = true; } 27 | remove { PropertyChangedEventRemoved = true; } 28 | } 29 | 30 | public bool PropertyChangedEventAdded; 31 | public bool PropertyChangedEventRemoved; 32 | 33 | public bool TestBool { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/View.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Gtk; 3 | using Binding.Gtk; 4 | 5 | namespace Binding.Test.TestData 6 | { 7 | public class View 8 | { 9 | public class WithoutCommandBinding : IWidget 10 | { 11 | public event PropertyChangedEventHandler PropertyChanged { add {} remove{} } 12 | 13 | public bool Sensitive { get; set; } 14 | 15 | public IStyleContext StyleContext => null; 16 | 17 | public IButton Button = null; 18 | } 19 | 20 | public class WithCommandBindingWithoutIButton : IWidget 21 | { 22 | public event PropertyChangedEventHandler PropertyChanged {add {} remove {}} 23 | 24 | public bool Sensitive { get; set; } 25 | 26 | public IStyleContext StyleContext => null; 27 | 28 | [CommandBinding(nameof(TestData.ViewModel.WithCommandProperty.CommandProperty))] 29 | public object Button = null; 30 | } 31 | 32 | public class WithCommandBinding : IWidget 33 | { 34 | public event PropertyChangedEventHandler PropertyChanged {add {} remove{}} 35 | 36 | public bool Sensitive { get; set; } 37 | 38 | public IStyleContext StyleContext => null; 39 | 40 | [CommandBinding(nameof(TestData.ViewModel.WithCommandProperty.CommandProperty))] 41 | public IButton Button; 42 | } 43 | 44 | public interface WithObjectProperty 45 | { 46 | object ObjectProperty { get; } 47 | } 48 | 49 | public interface WidgetWithObjectPropery : IWidget 50 | { 51 | object ObjectProperty { get; set;} 52 | } 53 | 54 | 55 | public interface WithoutINotifyPropertyChanged 56 | { 57 | object ObjectProperty { get; } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /BindingSharp.Test/TestData/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Input; 3 | 4 | namespace Binding.Test.TestData 5 | { 6 | public class ViewModel 7 | { 8 | public interface WithCommandProperty 9 | { 10 | object ObjectProperty { get; } 11 | ICommand CommandProperty { get; } 12 | } 13 | 14 | public interface WithoutINotifyPropertyChangedImplementation 15 | { 16 | object ObjectProperty { get; } 17 | } 18 | 19 | public interface WithINotifyPropertyChangedImplementation : INotifyPropertyChanged 20 | { 21 | object ObjectProperty { get; set; } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /BindingSharp/BindingSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | ../../GtkSharp/BuildOutput/Debug/GtkSharp.dll 10 | 11 | 12 | ../../GtkSharp/BuildOutput/Debug/GLibSharp.dll 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>$(AssemblyName).Test 19 | 20 | 21 | <_Parameter1>DynamicProxyGenAssembly2 22 | 23 | 24 | 25 | 26 | 27 | 28 | %(Filename)%(Extension) 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /BindingSharp/Core/BindINotifyPropertyChanged.IDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | 5 | namespace Binding.Core 6 | { 7 | internal partial class BindINotifyPropertyChanged : IDisposable 8 | { 9 | private bool disposedValue = false; 10 | 11 | protected virtual void Dispose(bool disposing) 12 | { 13 | if (!disposedValue) 14 | { 15 | if (disposing) 16 | { 17 | if(source != null) source.PropertyChanged -= OnSourcePropertyChanged; 18 | if(target != null && target is INotifyPropertyChanged vmNotify) vmNotify.PropertyChanged -= OnTargetPropertyChanged; 19 | } 20 | 21 | disposedValue = true; 22 | } 23 | } 24 | 25 | public void Dispose() 26 | { 27 | Dispose(true); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /BindingSharp/Core/BindINotifyPropertyChanged.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | 5 | namespace Binding.Core 6 | { 7 | internal partial class BindINotifyPropertyChanged : IBinder, IBinder 8 | { 9 | private object target; 10 | private PropertyInfo targetPropertyInfo; 11 | 12 | private INotifyPropertyChanged source; 13 | private PropertyInfo sourcePropertyInfo; 14 | 15 | public BindINotifyPropertyChanged(INotifyPropertyChanged source, string property) 16 | { 17 | this.source = source ?? throw new System.ArgumentNullException(nameof(source)); 18 | this.sourcePropertyInfo = source.GetType().GetProperty(property); 19 | 20 | if(sourcePropertyInfo == null) 21 | throw new BindingException(source, $"Property {property} is not a property of {nameof(source)}."); 22 | } 23 | 24 | public void Bind(INotifyPropertyChanged target, string property) 25 | { 26 | Bind((object)target, property); 27 | } 28 | 29 | public void Bind(object target, string property) 30 | { 31 | 32 | if(target == null) 33 | throw new ArgumentNullException(nameof(target)); 34 | 35 | targetPropertyInfo = target.GetType().GetProperty(property); 36 | 37 | if(targetPropertyInfo == null) 38 | throw new BindingException(target, $"Property {property} is not a property of {nameof(target)}."); 39 | 40 | this.target = target; 41 | if(target is INotifyPropertyChanged targetNotify) 42 | targetNotify.PropertyChanged += OnTargetPropertyChanged; 43 | 44 | source.PropertyChanged += OnSourcePropertyChanged; 45 | } 46 | 47 | protected void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs args) 48 | { 49 | if(args.PropertyName == sourcePropertyInfo.Name) 50 | { 51 | var value = sourcePropertyInfo.GetValue(sender); 52 | targetPropertyInfo.SetValue(target, value); 53 | } 54 | } 55 | 56 | protected void OnTargetPropertyChanged(object sender, PropertyChangedEventArgs args) 57 | { 58 | if(args.PropertyName == targetPropertyInfo.Name) 59 | { 60 | var value = targetPropertyInfo.GetValue(sender); 61 | sourcePropertyInfo.SetValue(source, value); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /BindingSharp/Core/BindingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Binding.Core 5 | { 6 | public class BindingException : Exception 7 | { 8 | public object Object { [ExcludeFromCodeCoverage] get; } 9 | 10 | public BindingException(object obj, string message) : base(message) 11 | { 12 | Object = obj; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BindingSharp/Core/IBinder.cs: -------------------------------------------------------------------------------- 1 | namespace Binding.Core 2 | { 3 | internal interface IBinder 4 | { 5 | void Bind(object target, string property); 6 | } 7 | 8 | internal interface IBinder 9 | { 10 | void Bind(T target, string property); 11 | } 12 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/BindButtonToCommand.IDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using Gtk; 4 | using Binding.Core; 5 | 6 | namespace Binding.Gtk 7 | { 8 | internal partial class BindButtonToCommand : IDisposable 9 | { 10 | private bool disposedValue = false; 11 | 12 | protected virtual void Dispose(bool disposing) 13 | { 14 | if (!disposedValue) 15 | { 16 | if (disposing) 17 | { 18 | if(button != null) button.Clicked -= OnButtonBlicked; 19 | if(command != null) command.CanExecuteChanged -= OnCommandCanExectueChanged; 20 | } 21 | 22 | disposedValue = true; 23 | } 24 | } 25 | 26 | public void Dispose() 27 | { 28 | Dispose(true); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/BindButtonToCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using Gtk; 4 | using Binding.Core; 5 | 6 | namespace Binding.Gtk 7 | { 8 | internal partial class BindButtonToCommand : IBinder 9 | { 10 | private readonly IButton button; 11 | private ICommand command; 12 | 13 | public BindButtonToCommand(IButton button) 14 | { 15 | this.button = button ?? throw new ArgumentNullException(nameof(button)); 16 | } 17 | 18 | public void Bind(object viewModel, string commandPropertyName) 19 | { 20 | if(viewModel == null) 21 | throw new ArgumentNullException(nameof(viewModel)); 22 | 23 | var viewModelCommandProperty = viewModel.GetType().GetProperty(commandPropertyName); 24 | 25 | if(viewModelCommandProperty == null) 26 | throw new BindingException(viewModel, $"Property {commandPropertyName} is not a property of {nameof(viewModel)}."); 27 | 28 | if(!typeof(ICommand).IsAssignableFrom(viewModelCommandProperty.PropertyType)) 29 | throw new BindingException(viewModel, $"Property {commandPropertyName} is not an {nameof(ICommand)}."); 30 | 31 | command = (ICommand) viewModelCommandProperty.GetValue(viewModel); 32 | command.CanExecuteChanged += OnCommandCanExectueChanged; 33 | 34 | button.Clicked += OnButtonBlicked; 35 | button.Sensitive = command.CanExecute(null); 36 | } 37 | 38 | private void OnButtonBlicked(object sender, EventArgs args) 39 | { 40 | if(command != null) 41 | { 42 | command.Execute(null); 43 | } 44 | } 45 | 46 | private void OnCommandCanExectueChanged(object sender, EventArgs args) 47 | { 48 | button.Sensitive = command.CanExecute(null); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/BindStyleContextToNotifyDataErrorInfo.IDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using Gtk; 5 | using Binding.Core; 6 | 7 | namespace Binding.Gtk 8 | { 9 | internal partial class BindStyleContextToNotifyDataErrorInfo : IDisposable 10 | { 11 | private bool disposedValue = false; 12 | 13 | protected virtual void Dispose(bool disposing) 14 | { 15 | if (!disposedValue) 16 | { 17 | if (disposing) 18 | { 19 | if(notifyDataErrorInfo != null) notifyDataErrorInfo.ErrorsChanged -= OnErrorsChanged; 20 | } 21 | 22 | disposedValue = true; 23 | } 24 | } 25 | 26 | public void Dispose() 27 | { 28 | Dispose(true); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/BindStyleContextToNotifyDataErrorInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using Gtk; 5 | using Binding.Core; 6 | 7 | namespace Binding.Gtk 8 | { 9 | internal partial class BindStyleContextToNotifyDataErrorInfo : IBinder, IBinder 10 | { 11 | private INotifyDataErrorInfo notifyDataErrorInfo; 12 | private readonly IStyleContext styleContext; 13 | private readonly string invalidCssClassName; 14 | private string propertyName; 15 | 16 | public BindStyleContextToNotifyDataErrorInfo(IStyleContext styleContext, string invalidCssClassName) 17 | { 18 | this.invalidCssClassName = invalidCssClassName ?? throw new ArgumentNullException(nameof(invalidCssClassName)); 19 | this.styleContext = styleContext ?? throw new System.ArgumentNullException(nameof(styleContext)); 20 | } 21 | 22 | public void Bind(object target, string property) 23 | { 24 | if(target == null) 25 | throw new ArgumentNullException(nameof(target)); 26 | else if(target is INotifyDataErrorInfo n) 27 | Bind(n, property); 28 | else 29 | throw new ArgumentException($"{nameof(target)} must be of type {nameof(INotifyDataErrorInfo)}", nameof(target)); 30 | } 31 | 32 | public void Bind(INotifyDataErrorInfo target, string property) 33 | { 34 | notifyDataErrorInfo = target ?? throw new ArgumentNullException(nameof(target)); 35 | propertyName = property ?? throw new ArgumentNullException(nameof(property)); 36 | 37 | target.ErrorsChanged += OnErrorsChanged; 38 | } 39 | 40 | private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) 41 | { 42 | if (e.PropertyName == propertyName && sender is INotifyDataErrorInfo target) 43 | { 44 | var errors = target.GetErrors(e.PropertyName); 45 | 46 | var hasErros = errors.Cast().Any(); 47 | var hasClass = styleContext.HasClass(invalidCssClassName); 48 | 49 | if (hasErros && !hasClass) 50 | { 51 | styleContext.AddClass(invalidCssClassName); 52 | } 53 | else if (!hasErros && hasClass) 54 | { 55 | styleContext.RemoveClass(invalidCssClassName); 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/CommandBindingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Binding.Gtk 4 | { 5 | [AttributeUsage(AttributeTargets.Field)] 6 | public class CommandBindingAttribute : Attribute 7 | { 8 | public string CommandProperty { get; } 9 | 10 | public CommandBindingAttribute(string commandProperty) 11 | { 12 | CommandProperty = commandProperty ?? throw new ArgumentNullException(nameof(commandProperty)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/PropertyBindingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Binding.Gtk 4 | { 5 | [AttributeUsage(AttributeTargets.Field)] 6 | public class PropertyBindingAttribute : Attribute 7 | { 8 | public string WidgetProperty { get; } 9 | public string ViewModelProperty { get; } 10 | 11 | public PropertyBindingAttribute(string widgetProperty, string viewModelProperty) 12 | { 13 | this.WidgetProperty = widgetProperty ?? throw new ArgumentNullException(nameof(widgetProperty)); 14 | this.ViewModelProperty = viewModelProperty ?? throw new ArgumentNullException(nameof(viewModelProperty)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/ValidationBindingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Binding.Gtk 4 | { 5 | [AttributeUsage(AttributeTargets.Field)] 6 | public class ValidationBindingAttribute : Attribute 7 | { 8 | public string Property { get; } 9 | 10 | public ValidationBindingAttribute(string property) 11 | { 12 | Property = property ?? throw new ArgumentNullException(nameof(property)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BindingSharp/Gtk/WidgetExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Reflection; 6 | using Gtk; 7 | using Binding.Core; 8 | 9 | namespace Binding.Gtk 10 | { 11 | public static class WidgetExtension 12 | { 13 | private static Dictionary> bindings = new Dictionary>(); 14 | 15 | #region Providers 16 | 17 | private static IStyleProvider styleProvider; 18 | internal static IStyleProvider StyleProvider 19 | { 20 | [ExcludeFromCodeCoverage] 21 | get 22 | { 23 | if(styleProvider == null) 24 | styleProvider = GetCssProvider(); 25 | 26 | return styleProvider; 27 | } 28 | set { styleProvider = value; } 29 | } 30 | 31 | private static Func styleContextBindingProvider; 32 | internal static Func StyleContextBindingProvider 33 | { 34 | [ExcludeFromCodeCoverage] 35 | get 36 | { 37 | if(styleContextBindingProvider == null) 38 | styleContextBindingProvider = GtkStyleContextBindingProvider; 39 | 40 | return styleContextBindingProvider; 41 | } 42 | set { styleContextBindingProvider = value; } 43 | } 44 | 45 | private static Func commandBindingProvider; 46 | internal static Func CommandBindingProvider 47 | { 48 | [ExcludeFromCodeCoverage] 49 | get 50 | { 51 | if(commandBindingProvider == null) 52 | commandBindingProvider = GtkCommandBindingProvider; 53 | 54 | return commandBindingProvider; 55 | } 56 | set { commandBindingProvider = value; } 57 | } 58 | 59 | private static Func widgetBindingProvider; 60 | internal static Func WidgetBindingProvider 61 | { 62 | [ExcludeFromCodeCoverage] 63 | get 64 | { 65 | if(widgetBindingProvider == null) 66 | widgetBindingProvider = GtkWidgetBindingProvider; 67 | 68 | return widgetBindingProvider; 69 | } 70 | set { widgetBindingProvider = value; } 71 | } 72 | 73 | [ExcludeFromCodeCoverage] 74 | private static CssProvider GetCssProvider() 75 | { 76 | var provider = new CssProvider(); 77 | 78 | using(var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("invalid.css")) 79 | using(var reader = new StreamReader(stream)) 80 | { 81 | provider.LoadFromData(reader.ReadToEnd()); 82 | } 83 | 84 | return provider; 85 | } 86 | 87 | [ExcludeFromCodeCoverage] 88 | private static IBinder GtkStyleContextBindingProvider(IStyleContext styleContext, string cssClasName) 89 | { 90 | return new BindStyleContextToNotifyDataErrorInfo(styleContext, cssClasName); 91 | } 92 | 93 | [ExcludeFromCodeCoverage] 94 | private static IBinder GtkCommandBindingProvider(IButton b) 95 | { 96 | return new BindButtonToCommand(b); 97 | } 98 | 99 | [ExcludeFromCodeCoverage] 100 | private static IBinder GtkWidgetBindingProvider(IWidget widget, string propertyName) 101 | { 102 | return new BindINotifyPropertyChanged(widget, propertyName); 103 | } 104 | #endregion Providers 105 | 106 | public static void Bind(this IWidget view, object obj) 107 | { 108 | var flags = System.Reflection.BindingFlags.Public; 109 | flags |= System.Reflection.BindingFlags.NonPublic; 110 | flags |= System.Reflection.BindingFlags.DeclaredOnly; 111 | flags |= System.Reflection.BindingFlags.Instance; 112 | 113 | var viewFields = view.GetType().GetFields(flags); 114 | 115 | foreach (var viewField in viewFields) 116 | { 117 | if(Attribute.IsDefined(viewField, typeof(CommandBindingAttribute))) 118 | BindCommand(view, obj, viewField); 119 | 120 | if(Attribute.IsDefined(viewField, typeof(PropertyBindingAttribute))) 121 | BindProperty(view, obj, viewField); 122 | 123 | if(Attribute.IsDefined(viewField, typeof(ValidationBindingAttribute))) 124 | BindValidation(view, obj, viewField); 125 | } 126 | } 127 | 128 | private static T GetViewFieldAs(IWidget view, FieldInfo viewField) 129 | { 130 | if(!typeof(T).IsAssignableFrom(viewField.FieldType)) 131 | throw new Exception("??"); 132 | 133 | return (T) viewField.GetValue(view); 134 | } 135 | 136 | private static T GetViewFieldAttribute(FieldInfo viewField) 137 | { 138 | var viewFieldBindingAttrs = viewField.GetCustomAttributes(typeof(T), false); 139 | 140 | if (viewFieldBindingAttrs.Length == 0) 141 | return default(T); 142 | else 143 | return (T)viewFieldBindingAttrs[0]; 144 | } 145 | 146 | private static void BindValidation(IWidget view, object viewModel, FieldInfo viewField) 147 | { 148 | var attribute = GetViewFieldAttribute(viewField); 149 | if(attribute != null) 150 | { 151 | var widget = GetViewFieldAs(view, viewField); 152 | var styleContext = widget.StyleContext; 153 | styleContext.AddProvider(StyleProvider, uint.MaxValue); 154 | 155 | var binder = StyleContextBindingProvider(styleContext, "invalid"); 156 | Bind(binder, view, viewModel, attribute.Property); 157 | } 158 | } 159 | 160 | private static void BindProperty(IWidget view, object viewModel, FieldInfo viewField) 161 | { 162 | var attribute = GetViewFieldAttribute(viewField); 163 | if(attribute != null) 164 | { 165 | var widget = GetViewFieldAs(view, viewField); 166 | var binder = WidgetBindingProvider(widget, attribute.WidgetProperty); 167 | Bind(binder, view, viewModel, attribute.ViewModelProperty); 168 | } 169 | } 170 | 171 | private static void BindCommand(IWidget view, object viewModel, FieldInfo viewField) 172 | { 173 | var attribute = GetViewFieldAttribute(viewField); 174 | if(attribute != null) 175 | { 176 | var button = GetViewFieldAs(view, viewField); 177 | var binder = CommandBindingProvider(button); 178 | Bind(binder, view, viewModel, attribute.CommandProperty); 179 | } 180 | } 181 | 182 | private static void Bind(IBinder binder, IWidget view, object viewModel, string propertyName) 183 | { 184 | binder.Bind(viewModel, propertyName); 185 | CacheBinder(view, binder); 186 | } 187 | 188 | private static void CacheBinder(IWidget view, IBinder binder) 189 | { 190 | if(!bindings.ContainsKey(view)) 191 | bindings[view] = new HashSet(); 192 | 193 | bindings[view].Add(binder); 194 | } 195 | 196 | /// 197 | /// Call this if disposing the widget 198 | // 199 | public static void DisposeBindings(this IWidget view) 200 | { 201 | if(bindings.ContainsKey(view)) 202 | { 203 | foreach(var binder in bindings[view]) 204 | { 205 | if(binder is IDisposable disposable) 206 | { 207 | disposable.Dispose(); 208 | } 209 | } 210 | bindings.Remove(view); 211 | } 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /BindingSharp/Resources/invalid.css: -------------------------------------------------------------------------------- 1 | .invalid { 2 | border-color: red; 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BindingSharp # 2 | 3 | > Note: As GtkSharp will probably not gain support for `INotifyPropertyChanged` this project will be rebased on [gir.core](https://github.com/gircore/gir.core) as it's successor as soon as there are Nuget packages available. 4 | 5 | BindingSharp enables MVVM-Style programming with [GTKSharp][]. It is a library to bind properties of a GTK widget to a viewmodel. 6 | 7 | If you have a .NET Standard 2.0 or .NET CORE application you can port it's view to [GTK][] and reuse the rest of your code to be deployed on Windows, Linux and MacOs with differnt native UI toolkits. 8 | 9 | [GtkSharp]: https://github.com/GtkSharp/GtkSharp 10 | [Gtk]: https://gtk.org 11 | 12 | ## Features ## 13 | * Binds properties of a [GTk.Widget][] to a viewmodel with a one-way or two-way binding via the [INotifyPropertyChanged][] interface 14 | * Special binding for a [Gtk.Button][] which can be bound to an [ICommand][] 15 | * Supports binding of a [GTk.Widget][] to a property of the viewmodel to support validation via the [INotifyDataErrorInfo][] interface (still work in progress) 16 | 17 | [Gtk.Widget]: https://developer.gnome.org/gtk3/stable/GtkWidget.html 18 | [Gtk.Button]: https://developer.gnome.org/gtk3/stable/GtkButton.html 19 | [ICommand]: https://docs.microsoft.com/de-de/dotnet/api/system.windows.input.icommand?view=netstandard-2.0 20 | [INotifyPropertyChanged]: https://docs.microsoft.com/de-de/dotnet/api/system.componentmodel.inotifypropertychanged?view=netstandard-2.0 21 | [INotifyDataErrorInfo]: https://docs.microsoft.com/de-de/dotnet/api/system.componentmodel.inotifydataerrorinfo?view=netstandard-2.0 22 | 23 | ## Using ## 24 | To use the binding the application must provide the viewmodel to the view to be able to create the binding inside the view. 25 | 26 | For a complete sample see the [Sample App](BindingSharp.Sample). 27 | 28 | 1. Create a view class with a matching glade file which describes the user interface as XML. Inside your view reference some UI widgets in fields. For working examples see the [templates][] of GtkSharp. 29 | 2. Add the _PropertyBindingAttribute_ or _CommandBindingAttribute_ or _ValidationBindingAttribute_ to a widget of your UI 30 | 3. Call _Bind(object obj)_ in your view's constructor to setup the binding 31 | 32 | public class MyWidget : Box 33 | { 34 | ... 35 | 36 | [UI] 37 | [CommandBinding(nameof(ViewModelClass.MyCommand))] 38 | private Button MyButton; 39 | 40 | public MyWidget(object viewmodel) : this(new Builder("MyWidget.glade")) 41 | { 42 | this.Bind(viewmodel) 43 | } 44 | 45 | ... 46 | } 47 | [templates]: https://github.com/GtkSharp/GtkSharp/tree/master/Source/Templates/GtkSharp.Template.CSharp/content 48 | 49 | ## Building from source ## 50 | 51 | --- 52 | **Note** 53 | 54 | Currently GtkSharp is missing the support for _INotifyPropertyChanged_ on _GLib.Object_. 55 | 56 | There is a [pull request][1] to add this feature. Until the pull request is merged you can use a custom version from GtkSharp from [my temporary fork][2]. 57 | 58 | To compile the code checkout the GTKSharp fork and put it parallel to this repository (see references in _*.csproj_ files) 59 | 60 | --- 61 | 62 | There are 3 projects inside the repository: 63 | - **BindingSharp:** Project source 64 | - **BindingSharp.Sample:** Example gtk application 65 | - **BindingSharp.Test:** Unit tests 66 | 67 | To build the source code run `dotnet build` in the corresponding project folder. 68 | 69 | To run the sample app execute `dotnet run` in the BindingSharp.Sample folder. 70 | 71 | To test the code run `dotnet test` in the BindingSharp.Test folder. 72 | 73 | [1]: https://github.com/GtkSharp/GtkSharp/pull/103 74 | [2]: https://github.com/badcel/GtkSharp/tree/InterfaceSupport 75 | 76 | ## License ## 77 | BindingSharp and its related components are licensed under [LGPL v2.1 license](LICENSE). 78 | --------------------------------------------------------------------------------