This sample may help you to understand the usage of PaletteSet, Palette and WPF UserControl and save you some time to investigate the how and why of building these controls. I think each of of us has spent a lot of time to get these controls working.
The focus on this sample is on the interaction between AutoCAD and the UserControl, NOT on the Contents of the UserControl. That's subject for another Forum Threat.
The sample show how to encapsulate all the required actions for a PaletteSet into one class: PaletteSetMultiplePalettes
I'll show you how to add two Palettes(and UserControls) in the PaletteSet to let you better understand the relation between PaletteSet, Palette(s) and the UserControl(s).
Pay attension to the building of the PaletteSet, Palette and UserControl in the function ShowPaletteSet(..). The set Dock, Visible(3X) is crusial for having the contents scaled proparly whithin the Palettes. Also note that the UserControl size is not defined, only the minimal Sizes.
The Palettes are AutoCAD document independant, meaning that there is only one PaletteSet for the running AutoCAD session. The UserControls will be updated for each active document. This is done in the DocumentActivated event AND triggered by the Layers_CollectionChanged event.
Also shown is how the WPF button Events and WpfData is handled by the Parent Class using the ButtonClickedEventHandler. This promotes the reuse of the UserControls.
This sample is build for AutoCAD 2016. Previous versions of AutoCAD may handle the building of the PaletteSets a bit different in requirds to the Size (DeviceIndependentSize) properties.
For this sample required referenced are
- AcCoreMgd, AcDbMgd, AcMgd, AcCui, AcCustomize, adWindows.
- PresentationFramework, PresentationCore, Xaml.
- Windows Forms, WindowsFormsIntegration.
After Compile, autoload into AutoCAD using the registry or simply use NETLOAD.
The Palettes will be shown with the command: ShowPsM.

Notice the Tabs in the PaletteSet for each Palette/UserControl.
Then create or open another drawing and notice the contents of the Palette get updated.
Create Layers or Text Styles and again the Palettes will be updated.
Press one of the OK buttons and the WPF contents will be send to the ucControlX_OnOkClicked event and written to the CommandLine.
Press the Cancel button and the Palette/UserControl will be removed from the PaletteSet. When both UserControls are cancelled the PalettSet will be removed as well.
Note that this sample is A way tho handle PaletteSets/Palettes and UserControl, not THE way of doing this.
I hope that other Authors of this forum comment on this template to improve.
Good Luck.
All Usings, repeat this in the other modules below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Autodesk.AutoCAD.Runtime; //accoremgd, acdbmgd, acmgd
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.LayerManager;
using Autodesk.AutoCAD.PlottingServices;
using Autodesk.AutoCAD.Publishing;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Windows.ToolPalette;
// renamings
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using AcAp = Autodesk.AutoCAD.ApplicationServices;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
using AcEd = Autodesk.AutoCAD.EditorInput;
using AcGe = Autodesk.AutoCAD.Geometry;
using AcRx = Autodesk.AutoCAD.Runtime;
using AcCm = Autodesk.AutoCAD.Colors;
using AcGi = Autodesk.AutoCAD.GraphicsInterface;
using AcLy = Autodesk.AutoCAD.LayerManager;
using AcPl = Autodesk.AutoCAD.PlottingServices;
using AcUi = Autodesk.AutoCAD.Windows;
using PlotType = Autodesk.AutoCAD.DatabaseServices.PlotType;
using AcWin = Autodesk.AutoCAD.Windows;
AcadApplication class: AcadInit
using System;
...
namespace AcadWpf
{
public class AcadInit : IExtensionApplication
{
public static DocumentCollection AcadDocMan = AcadApp.DocumentManager;
public static Dictionary<string, Delegate> RefreshPaletteContent { get; set; }
void IExtensionApplication.Terminate()
{
}
void IExtensionApplication.Initialize()
{
try
{
AcadDocMan.DocumentActivated += AcadDocMan_DocumentActivated;
AcadApp.UIBindings.Collections.Layers.CollectionChanged += Layers_CollectionChanged;
//Each Palette has to add it's own Refresh Command, like:
//public class PaletteSetMultiple
//... InfraApp.RefreshPaletteContent["PsMultiple"] = new Func<Document, bool>(RefreshPaletteContents);
//... public static bool RefreshPaletteContents(Document doc)
//
RefreshPaletteContent = new Dictionary<string, Delegate>(StringComparer.OrdinalIgnoreCase);
}
catch //(System.Exception ex)
{
//throw;
}
}
void Layers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var doc = AcadApp.DocumentManager.MdiActiveDocument;
if (doc != null)
PaletteSetMultiplePalettes.RefreshPaletteContents(doc);
}
void AcadDocMan_DocumentActivated(object sender, DocumentCollectionEventArgs e)
{
Document doc = e.Document;
if (doc == null)
return;
//Active Document is changed, refresh PaletteContent for visible PaletteSets
foreach (var paletteNameToRefresh in RefreshPaletteContent.Keys)
RefreshPaletteContent[paletteNameToRefresh].DynamicInvoke(doc);
}
}
}
PaletteSet/Palette Handler Class
using System;
...
namespace AcadWpf
{
public class PaletteSetMultiplePalettes
{
private static PaletteSet PsMultiple;
private static Palette Puc1;
private static Palette Puc2;
private static UserControl1 ucControl1;
private static UserControl2 ucControl2;
public PaletteSetMultiplePalettes(Document doc = null)
{
ShowPaletteSet(doc);
}
private static bool ShowPaletteSet(Document doc = null)
{
if (PsMultiple == null)
{
if (ucControl1 == null)
{
ucControl1 = new UserControl1();
ucControl1.OnOkClicked += ucControl1_OnOkClicked;
ucControl1.OnCancelClicked += ucControl1_OnCancelClicked;
}
if (ucControl2 == null)
{
ucControl2 = new UserControl2();
ucControl2.OnOkClicked += ucControl3_OnOkClicked;
ucControl2.OnCancelClicked += ucControl2_OnCancelClicked;
}
PsMultiple = new PaletteSet("PsMultiple", "ShowPsM", new Guid("81BE7D63-5FA9-4CA5-9418-7830CA53B766"));
PsMultiple.Dock = DockSides.None;
PsMultiple.DockEnabled = DockSides.None;
Puc1 = PsMultiple.AddVisual("Control1", ucControl1, true);
Puc2 = PsMultiple.AddVisual("Control2", ucControl2, true);
//Now the Control Is Added the ActualSize becomes available, and thus the best Palette(Set) size can be calculated
// Note the Palette need not to be visible.
var minPsWidth = (ucControl1.ActualWidth > ucControl2.ActualWidth) ? ucControl1.ActualWidth : ucControl2.ActualWidth;
var minPsHeight = (ucControl1.ActualHeight > ucControl2.ActualHeight) ? ucControl1.ActualHeight : ucControl2.ActualHeight;
//This Visible call must be executed several times in order to have the PaletteSet, Palette or it's Tabs to become visible
PsMultiple.Visible = true;
//Add a few units because the Palette(Set) need a bit more space than it's contents
minPsWidth += 120;
minPsHeight += 40;
PsMultiple.MinimumSize = new System.Drawing.Size((int)minPsWidth, (int)minPsHeight);
//Now centre te Palette into the AutoCAD Window.
var appWinPos = AcadApp.MainWindow.DeviceIndependentLocation;
var appWinSize = AcadApp.MainWindow.DeviceIndependentSize;
var xAppCenter = appWinPos.X + 0.5 * appWinSize.Width;
var yAppCenter = appWinPos.Y + 0.5 * appWinSize.Height;
PsMultiple.DeviceIndependentSize = new System.Windows.Size(minPsWidth - 200, minPsHeight - 150);
PsMultiple.DeviceIndependentLocation = new System.Windows.Point(xAppCenter - 0.5 * PsMultiple.DeviceIndependentSize.Width, yAppCenter - 0.5 * PsMultiple.DeviceIndependentSize.Height);
PsMultiple.Visible = true;
}
PsMultiple.Visible = true;
RefreshPaletteContents(doc);
//Register the RefreshCommand to be executed when AutoCAD changes it's active document
AcadInit.RefreshPaletteContent["PsMultiple"] = new Func<Document,bool>(RefreshPaletteContents);
return true;
}
public static bool RefreshPaletteContents(Document doc)
{
if (PsMultiple == null || !PsMultiple.Visible)
return false;
if (doc == null)
return false;
if (ucControl1 != null)
ucControl1.ShowData(doc);
if (ucControl2 != null)
ucControl2.ShowData(doc);
return true;
}
[CommandMethod("ShowPsM")]
public static void ShowPaletteSetMultiple()
{
Document doc = null;
Database db = null;
Editor ed = null;
try
{
doc = AcadApp.DocumentManager.MdiActiveDocument;
if (doc == null)
throw new System.Exception("No MdiActiveDocument");
db = doc.Database;
ed = doc.Editor;
ShowPaletteSet(doc);
}
catch (System.Exception ex)
{
if (ed != null)
ed.WriteMessage("\n Error: ShowPaletteSetMultiple: {0}", ex.Message);
else
System.Windows.Forms.MessageBox.Show(ex.Message, "ShowPaletteSetMultiple", System.Windows.Forms.MessageBoxButtons.OK);
}
}
private static void ucControl1_OnCancelClicked(object sender, EventArgs e)
{
ucControl1.OnOkClicked -= ucControl1_OnOkClicked;
ucControl1.OnCancelClicked -= ucControl1_OnCancelClicked;
ucControl1 = null;
Puc1 = null;
//Note: the current one is always index=0
PsMultiple.Remove(0);
if (ucControl2 == null)
{
PsMultiple.Visible = false;
PsMultiple.Close();
PsMultiple = null;
}
System.Windows.Forms.Application.DoEvents();
}
private static void ucControl1_OnOkClicked(object sender, EventArgs e)
{
var text = ucControl1.LayerText;
Document doc = AcadApp.DocumentManager.MdiActiveDocument;
if (doc != null)
{
Editor ed = doc.Editor;
ed.WriteMessage("\n TextBlock: {0}", text);
}
}
private static void ucControl2_OnCancelClicked(object sender, EventArgs e)
{
//Puc2 = null;
ucControl2.OnOkClicked -= ucControl3_OnOkClicked;
ucControl2.OnCancelClicked -= ucControl2_OnCancelClicked;
ucControl2 = null;
Puc2 = null;
//Note: the current one is always index=0
PsMultiple.Remove(0);
if (ucControl1 == null)
{
PsMultiple.Visible = false;
PsMultiple.Close();
PsMultiple = null;
}
System.Windows.Forms.Application.DoEvents();
}
private static void ucControl3_OnOkClicked(object sender, EventArgs e)
{
var text = ucControl2.StyleNames;
Document doc = AcadApp.DocumentManager.MdiActiveDocument;
if (doc != null)
{
Editor ed = doc.Editor;
ed.WriteMessage("\n TextBlock: {0}", text);
}
}
}
}<UserControl x:Class="AcadWpf.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="auto" Width="auto" MinHeight="120" MinWidth="400" d:DesignHeight="300" d:DesignWidth="400">
<Grid Background="White" >
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*" MinHeight="30" MaxHeight="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="1*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<TextBlock Name="tbkLayers" Text="{Binding LayerText}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" TextWrapping="Wrap" />
</Grid>
<Grid Grid.Row="1" Grid.ColumnSpan="2" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" >
<Button Name="btnOk" Content="OK" MinWidth="100" MaxWidth="150" Margin="5" Click="btnOk_Click"/>
<Button Name="btnCancel" Content="Cancel" MinWidth="100" MaxWidth="150" Margin="5" IsCancel="True" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>Using System;
...
namespace AcadWpf
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public delegate void ButtonClickedEventHandler(object sender, EventArgs e);
public event ButtonClickedEventHandler OnOkClicked;
public event ButtonClickedEventHandler OnCancelClicked;
public string LayerText { get; private set; }
public UserControl1()
{
InitializeComponent();
//No Binding for TextBlock. tbkLayers.ItemsSource = LayerText;
}
private void OnOkBtnClicked(object sender, EventArgs e)
{
// Delegate the event to the caller
if (OnOkClicked != null)
OnOkClicked(this, e);
}
private void OnCancelBtnClicked(object sender, EventArgs e)
{
// Delegate the event to the caller
if (OnCancelClicked != null)
OnCancelClicked(this, e);
}
public bool ShowData(Document doc)
{
if (doc == null)
return false;
Database db = doc.Database;
string docData = this.GetType().Name + ": Show Layer Data for: " + doc.Name + Environment.NewLine;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
docData += " Layers: " + layerTable.Cast<ObjectId>().Count() + Environment.NewLine;
foreach (var lyId in layerTable)
{
var layer = (LayerTableRecord)tr.GetObject(lyId, OpenMode.ForRead);
docData += " " + layer.Name + Environment.NewLine;
}
tr.Commit();
}
tbkLayers.Text = docData; //Binding doesn't seems to work for TextBlocks
btnCancel.Click -= new System.Windows.RoutedEventHandler(OnCancelClicked);
btnCancel.Click += new System.Windows.RoutedEventHandler(OnCancelClicked);
btnOk.Click -= new System.Windows.RoutedEventHandler(OnOkClicked);
btnOk.Click += new System.Windows.RoutedEventHandler(OnOkClicked);
return true;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
LayerText = tbkLayers.Text;
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
LayerText = null;
}
}
}
<UserControl x:Class="AcadWpf.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="auto" Width="auto" MinHeight="120" MinWidth="400" d:DesignHeight="300" d:DesignWidth="400">
<Grid Background="White" >
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*" MinHeight="30" MaxHeight="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="1*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<TextBlock Name="tbkStyles" Text="{Binding StyleNames}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" TextWrapping="Wrap" />
</Grid>
<Grid Grid.Row="1" Grid.ColumnSpan="2" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" >
<Button Name="btnOk" Content="OK" MinWidth="100" MaxWidth="150" Margin="5" Click="btnOk_Click"/>
<Button Name="btnCancel" Content="Cancel" MinWidth="100" MaxWidth="150" Margin="5" IsCancel="True" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>
Using System;
...
namespace AcadWpf
{
/// <summary>
/// Interaction logic for UserControl2.xaml
/// </summary>
public partial class UserControl2 : UserControl
{
public delegate void ButtonClickedEventHandler(object sender, EventArgs e);
public event ButtonClickedEventHandler OnOkClicked;
public event ButtonClickedEventHandler OnCancelClicked;
public string StyleNames{ get; private set; }
public UserControl2()
{
InitializeComponent();
//this.DataContext = this;
}
private void OnOkBtnClicked(object sender, EventArgs e)
{
// Delegate the event to the caller
if (OnOkClicked != null)
OnOkClicked(this, e);
}
private void OnCancelBtnClicked(object sender, EventArgs e)
{
// Delegate the event to the caller
if (OnCancelClicked != null)
OnCancelClicked(this, e);
}
public bool ShowData(Document doc)
{
if (doc == null)
return false;
Database db = doc.Database;
string docData = this.GetType().Name + ": Show TextStyle Data for: " + doc.Name + Environment.NewLine;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var textStyleTable = (TextStyleTable)tr.GetObject(db.TextStyleTableId, OpenMode.ForRead);
docData += " TextStyles: " + textStyleTable.Cast<ObjectId>().Count() + Environment.NewLine;
foreach (var styleId in textStyleTable)
{
var txtStyle = (TextStyleTableRecord)tr.GetObject(styleId, OpenMode.ForRead);
docData += " " + txtStyle.Name + Environment.NewLine;
}
tr.Commit();
}
tbkStyles.Text = docData; //Binding doesn't seems to work for TextBlocks
btnCancel.Click -= new System.Windows.RoutedEventHandler(OnCancelClicked);
btnCancel.Click += new System.Windows.RoutedEventHandler(OnCancelClicked);
btnOk.Click -= new System.Windows.RoutedEventHandler(OnOkClicked);
btnOk.Click += new System.Windows.RoutedEventHandler(OnOkClicked);
return true;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
StyleNames = tbkStyles.Text;
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
StyleNames = null;
}
}
}