Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

Commit

Permalink
New Menu and new menu items and associated icons. MEF support enhance…
Browse files Browse the repository at this point in the history
…ments and cleanup. (#295)

* Add file icon to .efmodel files in Solution Explorer

* Add file icon to .efmodel files in Solution Explorer

* ClassShape.cs: Honor 'Handled' for DoubleClick to allow MEF extensions to add handler. Set ImageGetter in compartment mappings once, allowing MEF extension to add customized images.
EFModelDiagram.cs: Record the mousedown location to allow placement of new shapes in diagram.
CommandSet.cs: Add new Solution Explorer menu with 'Generate Code'. Add new 'Add Entity' context menu item to diagram menu. Added Icons for both.
LayoutCommand.cs: make LayoutDiagram methods public so MEF extensions can call them.
Commands.vsct: Added Solution Explorer manu and cleanup.
DslPackage.csproj: Added new files.
  • Loading branch information
dcastenholz committed Aug 23, 2021
1 parent 2a03251 commit 4b78224
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 16 deletions.
8 changes: 5 additions & 3 deletions src/Dsl/CustomCode/Partials/ClassShape.cs
Expand Up @@ -11,7 +11,6 @@

namespace Sawczyn.EFDesigner.EFModel
{

public partial class ClassShape : IHighlightFromModelExplorer, IMouseActionTarget
{
/// <summary>
Expand Down Expand Up @@ -134,7 +133,7 @@ protected override CompartmentMapping[] GetCompartmentMappings(Type melType)

// Each item in the each compartment will call the appropriate method to determine its icon.
// This happens any time the element's presentation element invalidates.
foreach (ElementListCompartmentMapping mapping in mappings.OfType<ElementListCompartmentMapping>())
foreach (ElementListCompartmentMapping mapping in mappings.OfType<ElementListCompartmentMapping>().Where(mapping => mapping.ImageGetter == null))
mapping.ImageGetter = GetPropertyImage;

return mappings;
Expand Down Expand Up @@ -462,6 +461,10 @@ public override void OnDoubleClick(DiagramPointEventArgs e)
{
base.OnDoubleClick(e);

// Allow MEF Extension to mark the event as Handled
if(e.Handled)
return;

if (OpenCodeFile != null)
{
ModelClass modelClass = (ModelClass)ModelElement;
Expand All @@ -487,6 +490,5 @@ public override void OnDoubleClick(DiagramPointEventArgs e)
ErrorDisplay.Show(Store, $"Can't open generated file for {modelClass.Name}");
}
}

}
}
12 changes: 11 additions & 1 deletion src/Dsl/CustomCode/Partials/EFModelDiagram.cs
Expand Up @@ -302,5 +302,15 @@ public override void OnMouseUp(DiagramMouseEventArgs e)
IsDroppingExternal = false;
base.OnMouseUp(e);
}
}

/// <summary>Called by the control's OnMouseDown().</summary>
/// <param name="e">A DiagramMouseEventArgs that contains event data.</param>
public override void OnMouseDown(DiagramMouseEventArgs e)
{
MouseDownPosition = e.MousePosition;
base.OnMouseDown(e);
}

public PointD MouseDownPosition;
}
}
2 changes: 1 addition & 1 deletion src/Dsl/DslDefinition.dsl
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Dsl xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="9987f227-3d05-49b7-b151-857879f5dfb8" Description="Entity Framework visual editor for EF6, EFCore and beyond." Name="EFModel" DisplayName="Entity Framework Visual Editor" Namespace="Sawczyn.EFDesigner.EFModel" MajorVersion="3" Build="7" ProductName="EFDesigner" CompanyName="Michael Sawczyn" PackageGuid="56bbe1ba-aaee-4883-848f-e3c8656f8db2" PackageNamespace="Sawczyn.EFDesigner.EFModel" xmlns="http://schemas.microsoft.com/VisualStudio/2005/DslTools/DslDefinitionModel">
<Dsl xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="9987f227-3d05-49b7-b151-857879f5dfb8" Description="Entity Framework visual editor for EF6, EFCore and beyond." Name="EFModel" DisplayName="Entity Framework Visual Editor" Namespace="Sawczyn.EFDesigner.EFModel" MajorVersion="3" Build="7" Revision="1" ProductName="EFDesigner" CompanyName="Michael Sawczyn" PackageGuid="56bbe1ba-aaee-4883-848f-e3c8656f8db2" PackageNamespace="Sawczyn.EFDesigner.EFModel" xmlns="http://schemas.microsoft.com/VisualStudio/2005/DslTools/DslDefinitionModel">
<Classes>
<DomainClass Id="95532cb8-3452-4b09-a654-aeb2e2d0b3ad" Description="" Name="ModelRoot" DisplayName="Entity Model" Namespace="Sawczyn.EFDesigner.EFModel">
<CustomTypeDescriptor>
Expand Down
71 changes: 63 additions & 8 deletions src/DslPackage/Commands.vsct
Expand Up @@ -8,6 +8,7 @@
<!-- This causes Visual Studio to re-merge the menu definitions for the package. -->
<!-- Otherwise, changes won't take effect until the next time devenv /setup is run. -->
<!-- -->
<!-- ReSharper disable MarkupAttributeTypo -->
<Extern href="stdidcmd.h"/>
<Extern href="vsshlids.h"/>
<Extern href="virtkeys.h"/>
Expand Down Expand Up @@ -42,7 +43,14 @@

<Groups>

<Group guid="guidEFDiagramMenuCmdSet" id="grpidEFDiagram" priority="0x0100">
<!-- Solution explorer menu group-->
<Groups>
<Group guid="guidSolutionExplorerContextMenuCmdSet" id="grpidSolutionExplorerContextMenuGroup" priority="0x0000">
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE" />
</Group>
</Groups>

<Group guid="guidEFDiagramMenuCmdSet" id="grpidEFDiagram" priority="0x0100">
<!-- These symbols are defined in GeneratedVSCT.vsct -->
<Parent guid="guidCmdSet" id="menuidContext" />
</Group>
Expand All @@ -63,7 +71,7 @@

<Buttons>

<Button guid="guidEFDiagramMenuCmdSet" id="cmdidFind" priority="0x0100" type="Button">
<Button guid="guidEFDiagramMenuCmdSet" id="cmdidFind" priority="0x0100" type="Button">
<Parent guid="guidEFDiagramMenuCmdSet" id="grpidEFDiagram"/>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
Expand Down Expand Up @@ -106,6 +114,7 @@
<ButtonText>Generate Code</ButtonText>
<CommandName>GenerateCode</CommandName>
</Strings>
<Icon guid="guidImages" id="bmpGenerateCode" />
</Button>

<Button guid="guidEFDiagramMenuCmdSet" id="cmdidAddCodeProperties" priority="0x0105" type="Button">
Expand Down Expand Up @@ -283,7 +292,7 @@
<Parent guid="guidEFDiagramMenuCmdSet" id="grpidAlign"/>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Align Centers Horiz</ButtonText>
<ButtonText>Align Centers Horizontally</ButtonText>
<CommandName>AlignHCenter</CommandName>
</Strings>
</Button>
Expand All @@ -292,7 +301,7 @@
<Parent guid="guidEFDiagramMenuCmdSet" id="grpidAlign"/>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Align Centers Vert</ButtonText>
<ButtonText>Align Centers Vertically</ButtonText>
<CommandName>AlignVCenter</CommandName>
</Strings>
</Button>
Expand Down Expand Up @@ -332,6 +341,16 @@
<CommandName>EqualSpaceVert</CommandName>
</Strings>
</Button>

<Button guid="guidEFDiagramMenuCmdSet" id="cmdidAddEntity" priority="0x0108" type="Button">
<Parent guid="guidEFDiagramMenuCmdSet" id="grpidEFDiagram"/>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Add Entity</ButtonText>
<CommandName>AddEntity</CommandName>
</Strings>
<Icon guid="guidImages" id="bmpAddEntity" />
</Button>

<!-- Model explorer menu -->

Expand Down Expand Up @@ -362,7 +381,26 @@
</Strings>
</Button>

<!-- Solution explorer menu commands -->

<Button guid="guidSolutionExplorerContextMenuCmdSet" id="cmdidEFModelCommand1" priority="0x0100" type="Button">
<Parent guid="guidSolutionExplorerContextMenuCmdSet" id="grpidSolutionExplorerContextMenuGroup" />
<CommandFlag>DefaultInvisible</CommandFlag>
<CommandFlag>DynamicVisibility</CommandFlag>
<CommandFlag>DynamicItemStart</CommandFlag>
<Strings>
<ButtonText>Generate Code</ButtonText>
</Strings>
<Icon guid="guidImages" id="bmpGenerateCode" />
</Button>

</Buttons>

<!-- Solution explorer menu command bitmaps -->

<Bitmaps>
<Bitmap guid="guidImages" href="Resources\Commands.png" usedList="bmpGenerateCode, bmpAddEntity" />
</Bitmaps>
</Commands>

<KeyBindings>
Expand All @@ -385,6 +423,9 @@
</KeyBindings>

<VisibilityConstraints>
<!-- Automatically load the extension if user right-clicks on a .efmodel file -->
<VisibilityItem guid="guidSolutionExplorerContextMenuCmdSet" id="cmdidEFModelCommand1" context="guidEFModelUIContextGuid"/>

<!-- Ensures the command is only loaded for this DSL -->
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidFind" context="guidEditor"/>
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidLayoutDiagram" context="guidEditor"/>
Expand Down Expand Up @@ -418,19 +459,31 @@
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidResizeNarrowest" context="guidEditor"/>
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidEqualSpaceHoriz" context="guidEditor"/>
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidEqualSpaceVert" context="guidEditor"/>
<VisibilityItem guid="guidEFDiagramMenuCmdSet" id="cmdidAddEntity" context="guidEditor"/>
</VisibilityConstraints>

<Symbols>
<!-- Model explorer menu -->

<!-- Solution explorer menu -->
<GuidSymbol name="guidSolutionExplorerContextMenuCmdSet" value="{ac476e6f-82e2-482b-b514-e58e152c1c44}">
<IDSymbol name="grpidSolutionExplorerContextMenuGroup" value="0x0300" />
<IDSymbol name="cmdidEFModelCommand1" value="0x0301" />
</GuidSymbol>
<GuidSymbol name="guidEFModelUIContextGuid" value="{A9878D04-9B94-4FE0-9E18-8874CD56B187}" />

<!-- Solution explorer menu image -->
<GuidSymbol name="guidImages" value="{ab58b6f4-8673-44ef-8938-0dd466baf852}">
<IDSymbol name="bmpGenerateCode" value="1" />
<IDSymbol name="bmpAddEntity" value="2" />
</GuidSymbol>

<!-- Model explorer menu -->
<GuidSymbol name="guidMenuExplorerCmdSet" value="{922EC20C-4054-4E96-8C10-2405A1F91486}" >
<IDSymbol name="cmdidGoToCode" value="0x00203"/>
<IDSymbol name="cmdidExpandAll" value="0x00201"/>
<IDSymbol name="cmdidCollapseAll" value="0x00202"/>
</GuidSymbol>

<!-- Diagram menu -->

<GuidSymbol name="guidEFDiagramMenuCmdSet" value="{31178ecb-5da7-46cc-bd4a-ce4e5420bd3e}" >
<IDSymbol name="grpidEFDiagram" value="0x01001"/>

Expand All @@ -451,6 +504,7 @@
<IDSymbol name="cmdidAddForeignKeys" value="0x0001F"/>
<IDSymbol name="cmdidDelForeignKeys" value="0x00020"/>
<IDSymbol name="cmdidImageToClipboard" value="0x00021"/>
<IDSymbol name="cmdidAddEntity" value="0x00022"/>

<IDSymbol name="menuidSelect" value="0x01100"/>
<IDSymbol name="grpidSelect" value="0x01150"/>
Expand All @@ -477,4 +531,5 @@
<IDSymbol name="cmdidEqualSpaceVert" value="0x0010F"/>
</GuidSymbol>
</Symbols>
</CommandTable>
</CommandTable>
<!-- ReSharper restore MarkupAttributeTypo -->
167 changes: 167 additions & 0 deletions src/DslPackage/CustomCode/CommandHelper.cs
@@ -0,0 +1,167 @@
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.ComponentModel.Design;
using System.IO;
using System.Runtime.InteropServices;
using VSLangProj;

namespace Sawczyn.EFDesigner.EFModel
{
// NOTE: This could go into the existing partial file if desired. Might be more tidy and keep it all in one place.
// Uncomment the following line if the extension is converted to an async package.
//[ProvideAutoLoad(EFModelCommandSet.guidEFModelUIContextGuidString, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideUIContextRule(EFModelCommandSet.guidEFModelUIContextGuidString,
name : "EFModel auto load",
expression : "EFModel",
termNames : new[] {"EFModel"},
termValues : new[] {"HierSingleSelectionName:.efmodel$"})]
internal sealed partial class EFModelPackage { }

public static class CommandHelper
{
public static string EFModelerFileNameExtension = ".efmodel";
private const string TextTransformationFileExtension = ".tt";

public static string GetSingleFileSelectedPath()
{
if (!IsSingleProjectItemSelection(out IVsHierarchy hierarchy, out uint itemid)) return null;

// Get the file path
// ReSharper disable once SuspiciousTypeConversion.Global
((IVsProject)hierarchy).GetMkDocument(itemid, out string itemFullPath);
return itemFullPath;
}

private static bool IsSingleProjectItemSelection(out IVsHierarchy hierarchy, out uint itemid)
{
hierarchy = null;
itemid = VSConstants.VSITEMID_NIL;

IVsMonitorSelection monitorSelection = Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
IVsSolution solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
if (monitorSelection == null || solution == null)
{
return false;
}

IntPtr hierarchyPtr = IntPtr.Zero;
IntPtr selectionContainerPtr = IntPtr.Zero;

try
{
int hr = monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemid, out IVsMultiItemSelect multiItemSelect, out selectionContainerPtr);

if (ErrorHandler.Failed(hr) || hierarchyPtr == IntPtr.Zero || itemid == VSConstants.VSITEMID_NIL)
{
// there is no selection
return false;
}

// multiple items are selected
if (multiItemSelect != null) return false;

// there is a hierarchy root node selected, thus it is not a single item inside a project

if (itemid == VSConstants.VSITEMID_ROOT) return false;

hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
if (hierarchy == null) return false;

if (ErrorHandler.Failed(solution.GetGuidOfProject(hierarchy, out Guid _)))
{
return false; // hierarchy is not a project inside the Solution if it does not have a ProjectID Guid
}

// if we got this far then there is a single project item selected
return true;
}
finally
{
if (selectionContainerPtr != IntPtr.Zero)
{
Marshal.Release(selectionContainerPtr);
}

if (hierarchyPtr != IntPtr.Zero)
{
Marshal.Release(hierarchyPtr);
}
}
}

public static void VisibleAndEnabled(this MenuCommand menuCommand, bool visibleAndEnabled)
{
menuCommand.Visible = visibleAndEnabled;
menuCommand.Enabled = visibleAndEnabled;
}

public static bool HasExtension(this FileInfo fileInfo, string extension)
{
return string.Compare(fileInfo.Extension, extension, StringComparison.OrdinalIgnoreCase) == 0;
}

// parameter must be EntityContainer file path
public static void GenerateCode(string entityContainerFilepath)
{
DTE Dte = Package.GetGlobalService(typeof(DTE)) as DTE;
DTE2 Dte2 = Package.GetGlobalService(typeof(SDTE)) as DTE2;

ThreadHelper.ThrowIfNotOnUIThread();
if (entityContainerFilepath == null)
{
throw new ArgumentNullException(nameof(entityContainerFilepath));
}

if (!entityContainerFilepath.EndsWith(EFModelerFileNameExtension))
{
throw new ArgumentOutOfRangeException(nameof(entityContainerFilepath));
}

ProjectItem modelProjectItem = Dte2.Solution.FindProjectItem(entityContainerFilepath);

// Save file
if (Guid.Parse(modelProjectItem.Kind) == VSConstants.GUID_ItemType_PhysicalFile && !modelProjectItem.Saved)
{
try
{
modelProjectItem.Save();
}
catch (Exception)
{
throw new Exception($"Save failed for {modelProjectItem.Name}");
}
}

// TODO - clean up constant string
string templateFilename = Path.ChangeExtension(entityContainerFilepath, TextTransformationFileExtension);

ProjectItem templateProjectItem = Dte2.Solution.FindProjectItem(templateFilename);

VSProjectItem templateVsProjectItem = templateProjectItem?.Object as VSProjectItem;

if (templateVsProjectItem == null)
{
// TODO - place messages in output window or on PCML UI
throw new Exception($"Tried to generate code but couldn't find {templateFilename} in the solution.");
}

try
{
Dte.StatusBar.Text = $"Generating code from {templateFilename}";
templateVsProjectItem.RunCustomTool();
Dte.StatusBar.Text = $"Finished generating code from {templateFilename}";
}
catch (COMException)
{
string message = $"Encountered an error generating code from {templateFilename}. Please transform T4 template manually.";
Dte.StatusBar.Text = message;
// TODO - place messages in output window or on PCML UI
throw new Exception(message);
}
}
}
}

0 comments on commit 4b78224

Please sign in to comment.