From e9b60dbf099ab31643278b5de2e4d1904b06bd96 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 15 Jun 2013 13:50:24 +0200 Subject: [PATCH] add basic support for adding event handlers in FormsDesigner --- .../Project/CSharpBinding.csproj | 1 + .../Src/FormsDesigner/CSharpDesignerLoader.cs | 17 +- .../CSharpEventBindingService.cs | 153 ++++++++++++++++++ .../CSharpFormsDesignerLoaderContext.cs | 6 + .../ICSharpDesignerLoaderContext.cs | 1 + .../Src/Refactoring/CSharpCodeGenerator.cs | 8 +- .../Src/Refactoring/SDRefactoringContext.cs | 16 +- .../Project/Src/DesignerViewContent.cs | 7 +- 8 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpEventBindingService.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj index cb3de8ebaf..00e4af1efb 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj @@ -81,6 +81,7 @@ + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpDesignerLoader.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpDesignerLoader.cs index a570709dc8..02bfa62054 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpDesignerLoader.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpDesignerLoader.cs @@ -22,6 +22,8 @@ namespace CSharpBinding.FormsDesigner { public class CSharpDesignerLoader : AbstractCodeDomDesignerLoader { + IUnresolvedTypeDefinition primaryPart; + readonly CodeDomProvider codeDomProvider = new CSharpCodeProvider(); readonly ICSharpDesignerLoaderContext context; @@ -43,6 +45,18 @@ namespace CSharpBinding.FormsDesigner return base.IsReloadNeeded() || context.DesignerCodeFileDocument.Version.Equals(lastTextContentVersion); } + protected override void Initialize() + { + base.Initialize(); + + base.DesignerLoaderHost.AddService(typeof(System.ComponentModel.Design.IEventBindingService), new CSharpEventBindingService(context, base.DesignerLoaderHost, this)); + } + + public ITypeDefinition GetPrimaryTypeDefinition() + { + return primaryPart.Resolve(new SimpleTypeResolveContext(context.GetCompilation().MainAssembly)).GetDefinition(); + } + // Steps to load the designer: // - Parse main file // - Find other files containing parts of the form @@ -59,7 +73,6 @@ namespace CSharpBinding.FormsDesigner var compilation = context.GetCompilation(); // Find designer class - IUnresolvedTypeDefinition primaryPart; ITypeDefinition designerClass = FormsDesignerSecondaryDisplayBinding.GetDesignableClass(primaryParseInfo.UnresolvedFile, compilation, out primaryPart); IMethod initializeComponents = FormsDesignerSecondaryDisplayBinding.GetInitializeComponents(designerClass); @@ -115,7 +128,7 @@ namespace CSharpBinding.FormsDesigner LoggingService.Debug("NRefactoryDesignerLoader.Parse() finished"); if (!isFirstClassInFile) { - MessageService.ShowWarning("The form must be the first class in the file in order for form resources be compiled correctly.\n" + + MessageService.ShowWarning("The form must be the first class in the file in order for form resources to be compiled correctly.\n" + "Please move other classes below the form class definition or move them to other files."); } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpEventBindingService.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpEventBindingService.cs new file mode 100644 index 0000000000..504fdb1586 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpEventBindingService.cs @@ -0,0 +1,153 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Windows.Threading; +using ICSharpCode.Core; +using ICSharpCode.FormsDesigner.Gui.OptionPanels; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Gui; +using CSharpBinding.Refactoring; + +namespace CSharpBinding.FormsDesigner +{ + public class CSharpEventBindingService : System.ComponentModel.Design.EventBindingService + { + readonly CSharpDesignerLoader loader; + readonly ICSharpDesignerLoaderContext context; + + public CSharpEventBindingService(ICSharpDesignerLoaderContext context, IServiceProvider provider, CSharpDesignerLoader loader) : base(provider) + { + this.loader = loader; + if (context == null) + throw new ArgumentNullException("context"); + if (loader == null) + throw new ArgumentNullException("loader"); + this.context = context; + this.loader = loader; + } + + protected override string CreateUniqueMethodName(IComponent component, EventDescriptor e) + { + string componentName = GetComponentName(component); + return GetEventHandlerName(componentName, e.DisplayName); + } + + string GetComponentName(IComponent component) + { + string siteName = component.Site.Name; + return Char.ToUpper(siteName[0]) + siteName.Substring(1); + } + + string GetEventHandlerName(string componentName, string eventName) + { + string eventHandlerNameFormat = GetEventHandlerNameFormat(); + return String.Format(eventHandlerNameFormat, componentName, eventName); + } + + string GetEventHandlerNameFormat() + { + if (GeneralOptionsPanel.GenerateVisualStudioStyleEventHandlers) { + return "{0}_{1}"; + } + return "{0}{1}"; + } + + protected override ICollection GetCompatibleMethods(EventDescriptor e) + { + ITypeDefinition definition = loader.GetPrimaryTypeDefinition(); + ArrayList compatibleMethods = new ArrayList(); + MethodInfo methodInfo = e.EventType.GetMethod("Invoke"); + var methodInfoParameters = methodInfo.GetParameters(); + + foreach (IMethod method in definition.Methods) { + if (method.Parameters.Count == methodInfoParameters.Length) { + bool found = true; + for (int i = 0; i < methodInfoParameters.Length; ++i) { + ParameterInfo pInfo = methodInfoParameters[i]; + IParameter p = method.Parameters[i]; + if (p.Type.ReflectionName != pInfo.ParameterType.ToString()) { + found = false; + break; + } + } + if (found) { + compatibleMethods.Add(method.Name); + } + } + } + + return compatibleMethods; + } + + protected override bool ShowCode() + { + if (context != null) { + context.ShowSourceCode(); + return true; + } + return false; + } + + protected override bool ShowCode(int lineNumber) + { + if (context != null) { + context.ShowSourceCode(lineNumber); + return true; + } + return false; + } + + protected override bool ShowCode(IComponent component, EventDescriptor edesc, string methodName) + { + // There were reports of an ArgumentNullException caused by edesc==null. + // Looking at the .NET code calling this method, this can happen when there are two calls to ShowCode() before the Application.Idle + // event gets raised. In that case, ShowCode() already was called for the second set of arguments, and we can safely ignore + // the call with edesc==null. + if (context != null && edesc != null) { + // TODO : does not properly update events list in properties pad! + IEvent evt = FindEvent(edesc); + if (evt == null) return false; + context.ShowSourceCode(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate { InsertEventHandlerInternal(methodName, evt); }); + return true; + } + return false; + } + + void InsertEventHandlerInternal(string methodName, IEvent evt) + { + CSharpCodeGenerator generator = new CSharpCodeGenerator(); + var primary = loader.GetPrimaryTypeDefinition(); + var evtHandler = primary.GetMethods(m => m.Name == methodName, GetMemberOptions.IgnoreInheritedMembers).FirstOrDefault(); + if (evtHandler == null) { + generator.InsertEventHandler(primary, methodName, evt, true); + } + else { + CSharpBinding.Parser.CSharpFullParseInformation parseInfo; + var node = evtHandler.GetDeclaration(out parseInfo) as MethodDeclaration; + if (node != null && !node.Body.IsNull) { + var location = node.Body.FirstChild.StartLocation; + var firstStatement = node.Body.Children.OfType().FirstOrDefault(); + if (firstStatement != null) + location = firstStatement.StartLocation; + // TODO : does not jump correctly... + SD.FileService.JumpToFilePosition(new FileName(evtHandler.Region.FileName), location.Line, location.Column); + } + } + } + + IEvent FindEvent(EventDescriptor edesc) + { + var compilation = context.GetCompilation(); + var type = compilation.FindType(edesc.ComponentType); + return type.GetEvents(evt => evt.Name == edesc.Name).FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpFormsDesignerLoaderContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpFormsDesignerLoaderContext.cs index 6c96e32500..69dcc9ca30 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpFormsDesignerLoaderContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/CSharpFormsDesignerLoaderContext.cs @@ -7,6 +7,7 @@ using ICSharpCode.FormsDesigner; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; using CSharpBinding.Parser; namespace CSharpBinding.FormsDesigner @@ -51,6 +52,11 @@ namespace CSharpBinding.FormsDesigner } throw new InvalidOperationException("Designer file not found"); } + + public void ShowSourceCode(int lineNumber = 0) + { + viewContent.ShowSourceCode(lineNumber); + } } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/ICSharpDesignerLoaderContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/ICSharpDesignerLoaderContext.cs index 930c264fd3..10f70a724e 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/ICSharpDesignerLoaderContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormsDesigner/ICSharpDesignerLoaderContext.cs @@ -16,5 +16,6 @@ namespace CSharpBinding.FormsDesigner CSharpFullParseInformation GetPrimaryFileParseInformation(); ICompilation GetCompilation(); IDocument GetDocument(FileName fileName); + void ShowSourceCode(int lineNumber = 0); } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs index 7e3725f42f..1db7a6b82b 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/CSharpCodeGenerator.cs @@ -64,18 +64,20 @@ namespace CSharpBinding.Refactoring var view = SD.FileService.OpenFile(new FileName(match.Region.FileName), jumpTo); var editor = view.GetRequiredService(); var last = match.Members.LastOrDefault() ?? (IUnresolvedEntity)match; - var context = SDRefactoringContext.Create(editor.FileName, editor.Document, last.BodyRegion.End, CancellationToken.None); + editor.Caret.Location = last.BodyRegion.End; + var context = SDRefactoringContext.Create(editor, CancellationToken.None); var node = context.RootNode.GetNodeAt(last.Region.Begin); var resolver = context.GetResolverStateAfter(node); var builder = new TypeSystemAstBuilder(resolver); var delegateDecl = builder.ConvertEntity(eventDefinition.ReturnType.GetDefinition()) as DelegateDeclaration; if (delegateDecl == null) return; + var throwStmt = new ThrowStatement(new ObjectCreateExpression(context.CreateShortType("System", "NotImplementedException"))); var decl = new MethodDeclaration() { ReturnType = delegateDecl.ReturnType.Clone(), Name = name, Body = new BlockStatement() { - new ThrowStatement(new ObjectCreateExpression(context.CreateShortType("System", "NotImplementedException"))) + throwStmt } }; var param = delegateDecl.Parameters.Select(p => p.Clone()).OfType().ToArray(); @@ -88,7 +90,9 @@ namespace CSharpBinding.Refactoring // TODO InsertWithCursor not implemented! //script.InsertWithCursor("Insert event handler", Script.InsertPosition.End, decl).RunSynchronously(); } else { + // TODO does not jump correctly... script.InsertAfter(node, decl); + editor.JumpTo(throwStmt.StartLocation.Line, throwStmt.StartLocation.Column); } } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs index 2f097687b5..0cb3a459c4 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs @@ -33,7 +33,17 @@ namespace CSharpBinding.Refactoring public static SDRefactoringContext Create(ITextEditor editor, CancellationToken cancellationToken) { - return Create(editor.FileName, editor.Document, editor.Caret.Location, cancellationToken); + var parseInfo = SD.ParserService.Parse(editor.FileName, editor.Document, cancellationToken: cancellationToken) as CSharpFullParseInformation; + var compilation = SD.ParserService.GetCompilationForFile(editor.FileName); + CSharpAstResolver resolver; + if (parseInfo != null) { + resolver = parseInfo.GetResolver(compilation); + } else { + // create dummy refactoring context + resolver = new CSharpAstResolver(compilation, new SyntaxTree()); + } + var context = new SDRefactoringContext(editor, resolver, editor.Caret.Location, cancellationToken); + return context; } public static SDRefactoringContext Create(FileName fileName, ITextSource textSource, TextLocation location = default(TextLocation), CancellationToken cancellationToken = default(CancellationToken)) @@ -63,8 +73,8 @@ namespace CSharpBinding.Refactoring InitializeServices(); } - public SDRefactoringContext(ITextEditor editor, CSharpAstResolver resolver, TextLocation location) - : base(resolver, CancellationToken.None) + public SDRefactoringContext(ITextEditor editor, CSharpAstResolver resolver, TextLocation location, CancellationToken cancellationToken = default(CancellationToken)) + : base(resolver, cancellationToken) { this.resolver = resolver; this.editor = editor; diff --git a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs index 6e0959b695..d78e0ac34c 100644 --- a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs +++ b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs @@ -633,14 +633,9 @@ namespace ICSharpCode.FormsDesigner this.DesignerCodeFile.IsDirty = isDirty; } - public void ShowSourceCode() + public void ShowSourceCode(int lineNumber = 0) { this.WorkbenchWindow.ActiveViewContent = this.PrimaryViewContent; - } - - public void ShowSourceCode(int lineNumber) - { - ShowSourceCode(); ITextEditor editor = this.primaryViewContent.GetService(); if (editor != null) { editor.JumpTo(lineNumber, 1);