Problem Statement
A good friend asked me the following question:
How can I in Visio change the color to a previously selected color just by selecting a shape?
Sounds simple enough, but there are some caveats, so here is my attempt to tackle this problem. The main caveats were:
- hooking up the
SelectionChanged
event - keeping and accessing the state in the Ribbon (for the default color)
- setting the color of the selected shape
The code for this project can be found here.
Visio Add-In – Preparation
I decided to use Visual Studio 2017 to create a Visio Add-In. To do this, we need to install the “Office/SharePoint development” workload. Since Visual Studio 2017; the installer allows for modular installation, so we just need to add this to our installation.
Start Visual Studio Installer (Start -> type “Visual Studio Installer”). In the installer window, select “More > Modify”:
After a little while, this takes us to the workloads selection screen. Select Office/SharePoint development and then click “Modify”.
When you launch Visual Studio again, you’ll find a new bunch of project templates.
Creating the Visio Add-in
In VS, create a new project (File > New > Project…) like this:
As you can see, there are new project templates for “Office/SharePoint”. I choose the Visio Add-in project and gave it an appropriate name “ActOnShapeSelection
”.
The result is a project with one C# file (ThisAddIn.cs). This is where we will initialize our events. As a test, we show a message box when starting our add-in, and another one when closing it:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
MessageBox.Show("Startup ActOnShapeSelection");
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
MessageBox.Show("Shutdown ActOnShapeSelection");
}
}
Remark: By default, the namespace System.Windows.Forms
is not included in the using
list, so we will need to add it. An easy way to do this is by clicking on the red-underlined MessageBox
and typing ctrl+; (control + semicolon that is). Now we can choose how to solve the “using
problem”.
Starting the application (F5) will now start Visio and our first message box is indeed shown. Closing Visio shows the second message box. No rocket science so far, but this proves that our add-in is loaded properly into Visio.
Convincing Visio to do something when a shape is selected is just a bit harder.
Wiring the Selected Event
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.SelectionChanged += Application_SelectionChanged;
}
private void Application_SelectionChanged(Visio.Window Window)
{
MessageBox.Show("SelectionChanged ActOnShapeSelection");
}
}
The SelectionChanged
event must be wired in the startup
event. Later, we will do the same for the ShapeAdded
event. Once you know this “trick”, things become easy.
When running this code, each time we select something in Visio, we see our fancy message box. So the event wiring works. Now we want to be able to only execute code when a shape is selected. Let’s investigate if we can find out something about the selected object(s) in the “Window
” parameter:
As expected, this is a dynamic object. Visio is accessed over COM. Luckily, the debugger allows to expand the dynamic view members. Looking through the members of this object, we find a property “Selection
”. This looks promising! Looking a bit further, “Selection
” is an implementation of the interface
IVSelection
. And this interface
inherits from IEnumerable
.
So Selection
is actually an enumerable collection of all the selected items, hence it can be handled using a standard foreach( )
. Let’s try this:
private void Application_SelectionChanged(Visio.Window Window)
{
Visio.Selection selection = Window.Selection;
foreach (dynamic item in selection)
{
Visio.Shape shp = item as Visio.Shape;
if (shp != null)
{
shp.Characters.Text = "selected";
}
}
}
We run the add-in again (F5) and add 2 shapes on the page. When we select the shapes, they get the text “selected
”. So now we are capable of knowing which shapes are selected and doing something useful with them. Let’s add a new ribbon with actions to perform on our shapes. After all, that is the purpose of this exercise.
Adding a Ribbon
This can easily be done by right-clicking on the project, New Item. Then select Office/SharePoint > Ribbon (Visual Designer).
Name this ribbon “ActionsRibbon
.”
Opening the ribbon, we can now use the visual designer. Make sure that the toolbox window is visible (View > Toolbox).
Now we add 3 ToggleButton
s on the design surface, named btnRed
, btnGreen
, and you guessed it: btnBlue
. For each of the buttons, we add a Click
event by double-clicking on the button. Using the GroupView Tasks, we also add a DialogBoxLauncher
. This will open a ColorDialog
for selecting a custom color.
Double-click the group to implement the “DialogLauncherClick
” event, which will handle this.
The ActionsRibbon
will contain its own data, being the 3 components for a color (Red
, Green
, Blue
):
public byte Red { get; private set; }
public byte Green { get; private set; }
public byte Blue { get; private set; }
Each of the toggle buttons will toggle its own color component:
private void btnRed_Click(object sender, RibbonControlEventArgs e)
{
Red = (byte)(255 - Red);
}
private void btnGreen_Click(object sender, RibbonControlEventArgs e)
{
Green = (byte)(255 - Green);
}
private void btnBlue_Click(object sender, RibbonControlEventArgs e)
{
Blue = (byte)(255 - Blue);
}
Remark: This code works fine if there is no possibility for a custom color. When selecting a custom color, the values will become something other than 0
or 255
and not correspond to the UI anymore. I leave it as an exercise to the reader to make a better implementation.
Choosing a custom color:
private void group1_DialogLauncherClick(object sender, RibbonControlEventArgs e)
{
ColorDialog dlg = new ColorDialog { Color = Color.FromArgb(Red, Green, Blue) };
if (dlg.ShowDialog() == DialogResult.OK)
{
Red = dlg.Color.R;
Green = dlg.Color.G;
Blue = dlg.Color.B;
}
}
In the SelectionChanged
event of our AddIn
class, we now need to refer to the RGB values from the Ribbon
. Here is the full code for the event handler:
private void Application_SelectionChanged(Visio.Window Window)
{
ActionsRibbon rib = Globals.Ribbons.ActionsRibbon;
Visio.Selection selection = Window.Selection;
foreach (dynamic item in selection)
{
Visio.Shape shp = item as Visio.Shape;
if (shp != null)
{
shp.Characters.Text = "selected";
shp.CellsSRC[(short)Visio.VisSectionIndices.visSectionObject,3, 0].FormulaU =
$"THEMEGUARD(RGB({rib.Red}, {rib.Green}, {rib.Blue}))";
}
}
}
There is some Visio-fu to set the color. Consider this as a cookbook recipe. When you do this, you’ll get the desired result. Visio is not always straightforward, one could say.
Now we have a working Visio add-in, that does what was asked. BUT when we add a new shape, it will automatically receive the selected color. To solve this, we add another event handler:
Application.ShapeAdded += Application_ShapeAdded;
We also add a boolean indicating if we are adding a shape or not.
bool _isAddingAShape = false;
In the ShapeAdded
event, we set its value to true
:
private void Application_ShapeAdded(Visio.Shape Shape)
{
_isAddingAShape = true;
}
And we modify the SelectionChanged
event to do nothing when a shape was added. This event will be called when a shape is selected, AND when a shape is added (which indeed selects it). The code:
private void Application_SelectionChanged(Visio.Window Window)
{
if (! _isAddingAShape)
{
ActionsRibbon rib = Globals.Ribbons.ActionsRibbon;
Visio.Selection selection = Window.Selection;
foreach (dynamic item in selection)
{
Visio.Shape shp = item as Visio.Shape;
if (shp != null)
{
shp.Characters.Text = "selected";
shp.CellsSRC[(short)Visio.VisSectionIndices.visSectionObject, 3, 0].FormulaU =
$"THEMEGUARD(RGB({rib.Red}, {rib.Green}, {rib.Blue}))";
}
}
}
_isAddingAShape = false;
}
Conclusion
Using Visual Studio, it took us about 100 lines of code to implement the desired behavior. It would not be hard to add more actions to the ribbon. But in that case, it will be wise to move the code to perform the action into the ActionRibbon
class instead of using the values of this class in the AddIn
class.
The hardest part was to find how to obtain the newly created Ribbon
from within the AddIn
class. All the rest is just a straightforward implementation.