Creating pattern editor view using WPF and managed C++
From BuzzWiki
Contents |
Introduction
This is a very simple example how to create a machine with pattern editor view using WPF and managed C++. Thanks to Antonio who created an example project file.
Some benefits using managed C++ to create UI
- Managed and unmanaged code can be mixed together
- Communicate with .NET framework and components
- Use WPF to create rich user interfaces
- You can use Visual C++ Express Edition to create a Buzz machine
Please be aware that the users must have:
- Windows XP or later
- .NET 3.0 or later
Source and resource files
PianoRoll.xaml
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"> <Grid Margin="0,0,0,0"> <Canvas x:Name="PianoKeysCanvas" Width="300" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,21"> </Canvas> <Canvas Margin="300,1,0,24" Name="noteCanvas" HorizontalAlignment="Stretch" Width="Auto"> <Border BorderBrush="Black" BorderThickness="1"> </Border> </Canvas> <ScrollBar Height="19" HorizontalAlignment="Left" Name="scrollBar2" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="0,0,0,0"/> <ScrollBar HorizontalAlignment="right" Name="scrollBar1" Width="17" Margin="0,0,0,21" /> </Grid> </UserControl>
Gui.h
#pragma once
#include <windows.h>
#include <vcclr.h>
#include <math.h>
using namespace System;
using namespace System::Windows::Controls;
using namespace System::Windows::Threading;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
using namespace System::Windows::Shapes;
ref class ManagedGUI
{
public:
HwndSource ^HS;
UserControl ^Control;
int pianoKeyHeight;
double xScale, yScale;
ManagedGUI(UserControl ^uc)
{
Control = uc;
pianoKeyHeight = 30;
xScale = 1;
yScale = 1;
CreatePianoKeys();
CreatePianoGrid();
}
void CreatePianoKeys();
void CreatePianoGrid();
// RUNS IN AUDIO THREAD
// Don't modify the manged gui directly from audio thread. Send a notification etc.
void BuzzTick()
{
}
};
class GUI
{
public:
gcroot<ManagedGUI ^> MGUI;
HWND Window;
};
extern void CreateGUI(GUI &gui, HWND parent);
Gui.cpp
Creates the piano keys dynamically manipulating WPF GUI.
#include <windows.h>
#include "gui.h"
using namespace System;
using namespace System::IO;
using namespace System::Windows;
using namespace System::Windows::Controls;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
using namespace System::Windows::Markup;
using namespace System::Windows::Threading;
using namespace System::Runtime;
using namespace System::Runtime::InteropServices;
using namespace System::Reflection;
using namespace System::Collections::Generic;
using namespace System::Windows::Shapes;
HINSTANCE g_hInstance;
#pragma unmanaged
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
g_hInstance = hModule;
return TRUE;
}
#pragma managed
LRESULT WINAPI WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
{
CREATESTRUCT *cs = (CREATESTRUCT *)lParam;
GUI *pgui = (GUI *)cs->lpCreateParams;
::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pgui);
System::Windows::Interop::HwndSourceParameters^ sourceParams = gcnew System::Windows::Interop::HwndSourceParameters("sp");
sourceParams->ParentWindow = System::IntPtr(hwnd);
sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD;
System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(*sourceParams);
source->RootVisual = pgui->MGUI->Control;
pgui->MGUI->HS = source;
}
break;
case WM_SIZE:
{
GUI *pgui = (GUI *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
pgui->MGUI->Control->Width = LOWORD(lParam);
pgui->MGUI->Control->Height = HIWORD(lParam);
return TRUE;
}
break;
case WM_DESTROY:
{
GUI *pgui = (GUI *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
break;
}
return 0;
}
void CreateGUI(GUI &gui, HWND parent)
{
char mfn[_MAX_PATH];
GetModuleFileName(g_hInstance, mfn, _MAX_PATH);
char *pext = strstr(mfn, ".dll");
if (pext == NULL)
return;
strcpy_s(pext, 6, ".xaml");
String ^gfn;
gfn = Marshal::PtrToStringAnsi(System::IntPtr(mfn));
try
{
UserControl ^uc = (UserControl ^)XamlReader::Load(File::Open(gfn, FileMode::Open));
gui.MGUI = gcnew ManagedGUI(uc);
}
catch (Exception ^e)
{
System::Windows::MessageBox::Show(e->Message, gfn);
return;
}
static int count = 0;
if (!count++)
{
WNDCLASS windowClass;
windowClass.style = 0;
windowClass.lpfnWndProc = WindowProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = (HINSTANCE)g_hInstance;
windowClass.hIcon = 0;
windowClass.hCursor = 0;
windowClass.hbrBackground = 0;
windowClass.lpszMenuName = 0;
windowClass.lpszClassName = "limiterwndcls";
RegisterClass (&windowClass);
}
try
{
gui.Window = CreateWindowEx (0, "limiterwndcls", "limiterwnd", WS_CHILD | WS_VISIBLE,0, 0, 1, (int)gui.MGUI->Control->Height, parent, NULL, (HINSTANCE)g_hInstance, &gui);
}
catch (System::Exception^ e)
{
System::Windows::MessageBox::Show(e->Message);
}
}
void ManagedGUI::CreatePianoKeys()
{
System::Windows::Controls::Canvas ^pianoKeysCanvas = dynamic_cast<System::Windows::Controls::Canvas ^>(Control->FindName("PianoKeysCanvas"));
int key8count = 0;
for (int i = 0; i < 127; i++)
{
int key8 = i % 12;
bool iskey8 = (key8 == 0) | (key8 == 2) | (key8 == 4) | (key8 == 6) | (key8 == 7) | (key8 == 9) | (key8 == 11);
if (iskey8)
{
System::Windows::Shapes::Rectangle ^keyRect;
keyRect = gcnew System::Windows::Shapes::Rectangle();
keyRect->Height = pianoKeyHeight;
keyRect->Width = 300;
keyRect->Stroke = gcnew SolidColorBrush(Colors::Black);
keyRect->Fill = gcnew SolidColorBrush(Colors::White);
keyRect->SetValue(Canvas::TopProperty, (double)(key8count * pianoKeyHeight - 1) * yScale);
keyRect->SetValue(Canvas::LeftProperty, (double)0 * xScale);
keyRect->VerticalAlignment = VerticalAlignment::Top;
keyRect->HorizontalAlignment = HorizontalAlignment::Left;
pianoKeysCanvas->Children->Add(keyRect);
key8count++;
}
}
key8count = 0;
for (int i = 0; i < 127; i++)
{
int key8 = i % 12;
bool iskey8 = (key8 == 0) | (key8 == 2) | (key8 == 4) | (key8 == 6) | (key8 == 7) | (key8 == 9) | (key8 == 11);
if (!iskey8)
{
int blackHeight = pianoKeyHeight * 0.7;
System::Windows::Shapes::Rectangle ^keyRect;
keyRect = gcnew System::Windows::Shapes::Rectangle();
keyRect->Height = blackHeight;
keyRect->Width = 200;
keyRect->RadiusX = 4;
keyRect->RadiusY = 4;
keyRect->Stroke = gcnew SolidColorBrush(Colors::Black);
keyRect->Fill = gcnew SolidColorBrush(Colors::Black);
keyRect->SetValue(Canvas::TopProperty, (double)((key8count * pianoKeyHeight - 1) - blackHeight/2) * yScale);
keyRect->SetValue(Canvas::LeftProperty, (double)-20 * xScale);
keyRect->VerticalAlignment = VerticalAlignment::Top;
keyRect->HorizontalAlignment = HorizontalAlignment::Left;
pianoKeysCanvas->Children->Add(keyRect);
}
else
{
key8count++;
}
}
}
void ManagedGUI::CreatePianoGrid()
{
System::Windows::Controls::Canvas ^pianoKeysCanvas = dynamic_cast<System::Windows::Controls::Canvas ^>(Control->FindName("noteCanvas"));
int key8count = 0;
for (int i = 0; i < 127; i++)
{
int key8 = i % 12;
bool iskey8 = (key8 == 0) | (key8 == 2) | (key8 == 4) | (key8 == 6) | (key8 == 7) | (key8 == 9) | (key8 == 11);
if (iskey8)
{
System::Windows::Shapes::Rectangle ^keyRect;
keyRect = gcnew System::Windows::Shapes::Rectangle();
keyRect->Height = pianoKeyHeight/2;
keyRect->Width = 1600;
keyRect->Margin.Bottom = 0;
keyRect->Margin.Top = 0;
keyRect->Margin.Left = 0;
keyRect->Margin.Right = 0;
keyRect->Stroke = gcnew SolidColorBrush(Colors::LightBlue);
keyRect->Fill = gcnew SolidColorBrush(Colors::White);
keyRect->SetValue(Canvas::TopProperty, (double)(key8count * pianoKeyHeight - 1) * yScale);
keyRect->SetValue(Canvas::LeftProperty, (double)0 * xScale);
keyRect->VerticalAlignment = VerticalAlignment::Top;
keyRect->HorizontalAlignment = HorizontalAlignment::Left;
pianoKeysCanvas->Children->Add(keyRect);
key8count++;
}
else
{
System::Windows::Shapes::Rectangle ^keyRect;
keyRect = gcnew System::Windows::Shapes::Rectangle();
keyRect->Height = pianoKeyHeight/2;
keyRect->Width = 1600;
keyRect->Margin.Bottom = 0;
keyRect->Margin.Top = 0;
keyRect->Margin.Left = 0;
keyRect->Margin.Right = 0;
keyRect->Stroke = gcnew SolidColorBrush(Colors::LightBlue);
keyRect->Fill = gcnew SolidColorBrush(Colors::LightGray);
keyRect->SetValue(Canvas::TopProperty, (double)(key8count * pianoKeyHeight - 1 - pianoKeyHeight/2) * yScale);
keyRect->SetValue(Canvas::LeftProperty, (double)0 * xScale);
keyRect->VerticalAlignment = VerticalAlignment::Top;
keyRect->HorizontalAlignment = HorizontalAlignment::Left;
pianoKeysCanvas->Children->Add(keyRect);
}
}
for (int i = 0; i < 100; i++)
{
int beat = i % 4;
if (beat == 0)
{
System::Windows::Shapes::Line ^keyLine;
keyLine = gcnew System::Windows::Shapes::Line();
keyLine->Stroke = gcnew SolidColorBrush(Colors::Brown);
keyLine->X1 = i * 30;
keyLine->Y1 = 0;
keyLine->X2 = i * 30;
keyLine->Y2 = 1000;
pianoKeysCanvas->Children->Add(keyLine);
}
else
{
System::Windows::Shapes::Line ^keyLine;
keyLine = gcnew System::Windows::Shapes::Line();
keyLine->Stroke = gcnew SolidColorBrush(Colors::LightGray);
keyLine->X1 = i * 30;
keyLine->Y1 = 0;
keyLine->X2 = i * 30;
keyLine->Y2 = 1000;
pianoKeysCanvas->Children->Add(keyLine);
}
}
}
PianoRoll.cpp
Very light Buzz machine implementation. Does nothing.
#define _USE_MATH_DEFINES
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include "MachineInterface.h"
#include "GUI.h"
CMachineParameter const paraThreshold = { pt_byte, "Threshold", "Threshold", 0, 127, 255, MPF_STATE, 90 };
CMachineParameter const *pParameters[] = { ¶Threshold };
#pragma pack(1)
class gvals
{
public:
byte threshold;
};
#pragma pack()
CMachineInfo const MacInfo =
{
MT_GENERATOR, // type
MI_VERSION,
MIF_PATTERN_EDITOR | MIF_PE_NO_CLIENT_EDGE | MIF_NO_OUTPUT | MIF_CONTROL_MACHINE, // flags
0, // min tracks
0, // max tracks
1, // numGlobalParameters
0, // numTrackParameters
pParameters,
0,
NULL,
#ifdef _DEBUG
"WDE PianoRoll (Debug build)", // name
#else
"WDE PianoRoll", // name
#endif
"PianoRoll", // short name
"WDE", // author
NULL
};
class mi;
class miex : public CMachineInterfaceEx
{
public:
virtual void *CreatePatternEditor(void *parenthwnd);
mi *pmi;
};
class mi : public CMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Init(CMachineDataInput * const pi);
virtual void Tick();
miex ex;
GUI gui;
gvals gval;
};
void *miex::CreatePatternEditor(void *parenthwnd)
{
pmi->gui.Window = 0;
CreateGUI(pmi->gui, (HWND)parenthwnd);
return pmi->gui.Window;
}
DLL_EXPORTS
mi::mi()
{
ex.pmi = this;
GlobalVals = &gval;
AttrVals = NULL;
}
mi::~mi()
{
}
void mi::Init(CMachineDataInput * const pi)
{
pCB->SetMachineInterfaceEx(&ex);
pCB->WriteLine("INIT");
}
void mi::Tick()
{
// Example how to call a method from managed gui
gui.MGUI->BuzzTick();
}
PianoRoll.vcproj
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Name="PianoRoll"
ProjectGUID="{B615CA66-0381-4E69-AF27-D8DE020E54A0}"
RootNamespace="Limiter"
Keyword="Win32Proj"
TargetFrameworkVersion="196613"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="C:\Program Files\Jeskola\Buzz\Gear\Effects"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="0"
ManagedExtensions="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
CommandLine=""
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;LIMITER_EXPORTS"
MinimalRebuild="false"
BasicRuntimeChecks="0"
RuntimeLibrary="3"
StructMemberAlignment="0"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
CallingConvention="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
LinkIncremental="2"
GenerateDebugInformation="true"
AssemblyDebug="1"
SubSystem="2"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="copy PianoRoll.xaml "C:\Program Files\Jeskola\Buzz\Gear\Effects\""
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="0"
ManagedExtensions="1"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;LIMITER_EXPORTS"
RuntimeLibrary="2"
BufferSecurityCheck="false"
EnableFunctionLevelLinking="true"
FloatingPointModel="2"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
CallingConvention="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="h:\projects\buzz\rel\1.1\gear\effects\Jeskola Limiter.dll"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="2"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
<AssemblyReference
RelativePath="PresentationCore.dll"
AssemblyName="PresentationCore, Version=3.0.0.0, PublicKeyToken=31bf3856ad364e35, processorArchitecture=IA64"
MinFrameworkVersion="196608"
/>
<AssemblyReference
RelativePath="PresentationFramework.dll"
AssemblyName="PresentationFramework, Version=3.0.0.0, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
MinFrameworkVersion="196608"
/>
<AssemblyReference
RelativePath="UIAutomationProvider.dll"
AssemblyName="UIAutomationProvider, Version=3.0.0.0, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
MinFrameworkVersion="196608"
/>
<AssemblyReference
RelativePath="UIAutomationTypes.dll"
AssemblyName="UIAutomationTypes, Version=3.0.0.0, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
MinFrameworkVersion="196608"
/>
<AssemblyReference
RelativePath="System.dll"
AssemblyName="System, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"
MinFrameworkVersion="131072"
/>
<AssemblyReference
RelativePath="WindowsBase.dll"
AssemblyName="WindowsBase, Version=3.0.0.0, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
MinFrameworkVersion="196608"
/>
<AssemblyReference
RelativePath="System.Data.dll"
AssemblyName="System.Data, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86"
MinFrameworkVersion="131072"
/>
<AssemblyReference
RelativePath="System.Drawing.dll"
AssemblyName="System.Drawing, Version=2.0.0.0, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
MinFrameworkVersion="131072"
/>
<AssemblyReference
RelativePath="System.Windows.Forms.dll"
AssemblyName="System.Windows.Forms, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"
MinFrameworkVersion="131072"
/>
<AssemblyReference
RelativePath="System.XML.dll"
AssemblyName="System.Xml, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"
MinFrameworkVersion="131072"
/>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\GUI.cpp"
>
</File>
<File
RelativePath=".\PianoRoll.cpp"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath=".\GUI.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
<File
RelativePath=".\PianoRoll.xaml"
>
</File>
<File
RelativePath=".\ReadMe.txt"
>
</File>
</Files>
<Globals>
</Globals>
</VisualStudioProject>
Final words
Thats it. Remember to copy the xaml file to the same folder where the machine dll is. Now go and explore WPF and .NET possibilities!
