Archicad C++ API
About Archicad add-on development using the C++ API.
SOLVED!

Modeless Palette ac28

tomswons
Contributor

I made several applications with model dialogs  but i strugle to understand the core functions for creating a modeless pallete. I saw posts from older AC version but i cant convert it to work with ac 28. DG_Test is far too complex for me to understand what's going on. Can anyone help me create basic dockable pallet which have close pallet and ok button. For ok button i want warning msg "it works" and that it from this point on i will probably figure it out. Also for now when i click on workspace with for example mep tool archicad crash. I cant close palette neither button works. 

 

2 ACCEPTED SOLUTIONS

Accepted Solutions
Solution
tomswons
Contributor

Based on i kinda figuret it out to get it work.
https://community.graphisoft.com/t5/Archicad-C-API/SOLVED-How-to-make-a-Palette-dialog-by-C-Class/m-...

Not sure if it is correct apporach becouse as far as i can see in DG_test menaging functionallity of buttons etc is diffrent then in this example above. Are there gonna be any troubles with this apporach in the future or can i stick with it?


 

 

View solution in original post

Solution
Miklos Vegh
Graphisoft
Graphisoft

Here is a minimal implementation of a garbage collected palette. Using the DG_Test owner drawn listbox palette code as a template is not straightforward, as when the close button is pressed on it the palette instance will not be destroyed just get a hidden state and still continue to use system resources. With the following code the palette will be destroyed properly on a close interaction or call.

 

The resource looks like this:

'GDLG'  DGTEST_MINIMALPALETTE_RESID  Palette | grow | close	    0    0  200  100  "Minimal Palette Example" {
/* [  1] */ Button				  110   70   80   24	LargePlain  "Close"
/* [  2] */ LeftText			    4    4  192   60	LargePlain  StaticEdge  "This is a minimal DGPalette example dialog.\n\n"\
																				"The palette is visible only above the FloorPlan and the 3D Window."
}

'DLGH'  DGTEST_MINIMALPALETTE_RESID  Minimal_Palette_Example {
1	"Close Palette"				CloseButton
2	""							InfoText
}

The resource contains a static control and a button only. I implemented it in the DG_Test addon source so the palette ID follows the convention of that addon.

In order the palette to be dockable it has the resizeable flag.

 

In the code there are 2 objects. The first is a singleton manager object which is publicly available. The other is the palette object that is accessible only through the manager object, therefore both of its declartion and the definition is in the cpp file.

 

The header file (MinimalPalette.hpp):

// =====================================================================================================================
// MinimalPalette.hpp
// =====================================================================================================================

#if !defined (MINIMALPALETTE_HPP)
#define MINIMALPALETTE_HPP

#pragma once

#include "ACAPinc.h"
#include "APIEnvir.h"
#include "DGModule.hpp"
#include "DisposableObject.hpp"


class MinimalPalette;


// === class MinimalPaletteManager =====================================================================================

class MinimalPaletteManager: public GS::DisposeHandler
{
private:
	MinimalPalette* paletteInstance;

	MinimalPaletteManager ();

	MinimalPaletteManager (const MinimalPaletteManager& source) = delete;
	MinimalPaletteManager operator= (const MinimalPaletteManager& source) = delete;

	MinimalPalette& GetOrCreatePaletteInstance ();
	virtual void DisposeRequested (GS::DisposableObject& source) override;

public:
	virtual ~MinimalPaletteManager ();

	static const GS::Guid&	GetPaletteGuid ();
	static Int32			GetPaletteRefId ();

	static GSErrCode		PaletteAPIControlCallBack (Int32 referenceID, API_PaletteMessageID messageID, GS::IntPtr param);

	static MinimalPaletteManager& GetInstance ();

	bool	IsPaletteVisible () const;
	void	SetPaletteItemsStatus (bool enabled);

	void	ShowPalette ();
	void	ToggleVisibility ();
	void	ClosePalette ();
};

#endif // MINIMALPALETTE_HPP

 

The cpp file (MinimalPalette.cpp):

// =====================================================================================================================
// MinimalPalette.cpp
// =====================================================================================================================

#include "DGTestResIDs.hpp"
#include "DisposableObject.hpp"
#include "MinimalPalette.hpp"


// === MinimalPalette declaration=======================================================================================

class MinimalPalette: public DG::Palette,
					  public DG::PanelObserver,
					  public DG::ButtonItemObserver,
					  public GS::DisposableObject
{
private:
	enum {
		CloseButtonId	= 1,
		InfoTextId		= 2
	};

	DG::Button		closeButton;
	DG::LeftText	infoText;

	MinimalPalette ();

protected:
	virtual void	PanelOpened (const DG::PanelOpenEvent& ev) override;
	virtual	void	PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool* accepted) override;

	virtual void	ButtonClicked (const DG::ButtonClickEvent& ev) override;

public:
	MinimalPalette (const GS::Guid& paletteGuid);
	virtual ~MinimalPalette ();

	MinimalPalette (const MinimalPalette& source) = delete;
	MinimalPalette operator= (const MinimalPalette& source) = delete;

	void	Close ();
};


// === MinimalPaletteManager Implementation ============================================================================

MinimalPaletteManager::MinimalPaletteManager ():
	paletteInstance (nullptr)
{
}


MinimalPaletteManager::~MinimalPaletteManager ()
{
	PRECOND (paletteInstance == nullptr);
}


MinimalPalette& MinimalPaletteManager::GetOrCreatePaletteInstance ()
{
	if (paletteInstance == nullptr) {
		paletteInstance = new MinimalPalette (GetPaletteGuid ());
	}
	return *paletteInstance;
}


void MinimalPaletteManager::DisposeRequested (GS::DisposableObject& source)
{
	if (&source == paletteInstance) {
		paletteInstance = nullptr;
	}
}


const GS::Guid& MinimalPaletteManager::GetPaletteGuid ()
{
	static GS::Guid guid ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); // Please generate a unique guid here!
	return guid;
}


Int32 MinimalPaletteManager::GetPaletteRefId ()
{
	static Int32 refId (GS::CalculateHashValue (GetPaletteGuid ()));
	return refId;
}


GSErrCode MinimalPaletteManager::PaletteAPIControlCallBack (Int32 referenceID, API_PaletteMessageID messageID, GS::IntPtr param)
{
	static bool paletteOpenAtClose = false;
	GSErrCode err = NoError;
	if (referenceID == MinimalPaletteManager::GetPaletteRefId ()) {
		MinimalPaletteManager& paletteManager = MinimalPaletteManager::GetInstance ();

		switch (messageID) {
			case APIPalMsg_ClosePalette:
				paletteManager.ClosePalette ();
				break;

			case APIPalMsg_HidePalette_Begin:
				if (paletteManager.IsPaletteVisible ()) {
					paletteOpenAtClose = true;
					paletteManager.GetOrCreatePaletteInstance ().Hide ();
				} else {
					paletteOpenAtClose = false;
				}
				break;

			case APIPalMsg_OpenPalette:
				paletteOpenAtClose = true;
				[[fallthrough]];

			case APIPalMsg_HidePalette_End:
				if (paletteOpenAtClose && !paletteManager.IsPaletteVisible ()) {
					paletteManager.GetOrCreatePaletteInstance ().Show ();
				}
				break;

			case APIPalMsg_DisableItems_Begin:
				paletteManager.SetPaletteItemsStatus (false);
				break;

			case APIPalMsg_DisableItems_End:
				paletteManager.SetPaletteItemsStatus (true);
				break;

			case APIPalMsg_IsPaletteVisible:
				(*reinterpret_cast<bool*> (param)) = paletteManager.IsPaletteVisible ();
				break;

			default:
				break;
		}
	}

	return err;
}


MinimalPaletteManager& MinimalPaletteManager::GetInstance ()
{
	static MinimalPaletteManager managerSingletonInstance;

	return managerSingletonInstance;
}


bool MinimalPaletteManager::IsPaletteVisible () const
{
	return paletteInstance != nullptr && paletteInstance->IsVisible ();
}


void MinimalPaletteManager::SetPaletteItemsStatus (bool enabled)
{
	if (paletteInstance != nullptr) {
		if (enabled) {
			paletteInstance->EnableItems ();
		} else {
			paletteInstance->DisableItems ();
		}
	}
}


void MinimalPaletteManager::ShowPalette ()
{
	GetOrCreatePaletteInstance ().Show ();
}


void MinimalPaletteManager::ToggleVisibility ()
{
	if (IsPaletteVisible ()) {
		ClosePalette ();
	} else {
		ShowPalette ();
	}
}


void MinimalPaletteManager::ClosePalette ()
{
	if (paletteInstance != nullptr) {
		paletteInstance->Close ();
	}
}


// === MinimalPalette implementation ===================================================================================

MinimalPalette::MinimalPalette (const GS::Guid& paletteGuid):
	DG::Palette (ACAPI_GetOwnResModule (), DGTEST_MINIMALPALETTE_RESID, ACAPI_GetOwnResModule (), paletteGuid),
	closeButton (GetReference (), CloseButtonId),
	infoText (GetReference (), InfoTextId)
{
	SetDefaultGarbageCollector ();
	SetDisposeHandler (MinimalPaletteManager::GetInstance ());

	AutoResizeBlock (infoText.HVGrow, closeButton.HVMove);

	this->Attach (*this);
	closeButton.Attach (*this);

	this->BeginEventProcessing ();
}


MinimalPalette::~MinimalPalette ()
{
	this->Detach (*this);
	closeButton.Detach (*this);
}


void MinimalPalette::Close ()
{
	SendCloseRequest ();
}


void MinimalPalette::PanelOpened (const DG::PanelOpenEvent& /*ev*/)
{
//	if (ev.GetSource () != this) {
//		return;
//	}
//	SetClientSize (GetOriginalClientWidth (), GetOriginalClientHeight ()); // Restores the size defined in the resource
}


void MinimalPalette::PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool* /*accepted*/)
{
	if (ev.GetSource () != this) {
		return;
	}
	Hide ();
	EndEventProcessing ();
	MarkAsDisposable ();
}


void MinimalPalette::ButtonClicked (const DG::ButtonClickEvent& ev)
{
	if (ev.GetSource () == &closeButton) {
		Close ();
	}
}

 

Some details about how it works:

  • The manager object is descended from the DisposeHandler and implements its DisposeRequested function. DisposeRequested is called when the palette object is marked as disposable by calling MarkAsDisposable. From that time on the palette is treated as a non existing object although it will be deleted later in the event loop. The MarkAsDisposable is called when the user presses the close button.
  • Why do we need to use garbage collector? The palette object can not be deleted in its own PanelCloseRequested member function or in any other member function of itself, an other object is needed to keep track of the palette object pointer or reference.
  • The palette manager object provides interface to control palette. The palette directly is not accessible.
  • In the palette constructor the manager object is set as a dispose handler by calling SetDisposeHandler. That means the manager will be notified about the palette was marked as disposable so it can clear the palette pointer/reference. The default garbage collector is need to be set to the palette by SetDefaultGarbageCollector.
  • The MinimalPalette::Close member function is called through the manager object when the user wants to close the palette. This function calls the SendCloseRequest that results a PanelCloseRequested (this is called also when the X button is pressed in the palette caption).
  • In the PanelCloseRequested there are the Hide / EndEventProcessing / MarkAsDisposable calls. The order of them is important. Hide must be called to avoid the palette to get any events from that moment.
  • The garbage is destroyed in the AC event loop.

The palette can be registered and unregistered with these calls:

	ACAPI_RegisterModelessWindow (MinimalPaletteManager::GetPaletteRefId (),
								  MinimalPaletteManager::PaletteAPIControlCallBack,
								  API_PalEnabled_FloorPlan + API_PalEnabled_3D,
								  GSGuid2APIGuid (MinimalPaletteManager::GetPaletteGuid ()));

	ACAPI_UnregisterModelessWindow (MinimalPaletteManager::GetPaletteRefId ());

Here the palette is set to be visible only above the FloorPlan and the 3D Window.

You can open and close the palette with these functions:

	MinimalPaletteManager::GetInstance ().ShowPalette ();
	MinimalPaletteManager::GetInstance ().ClosePalette ();

View solution in original post

2 REPLIES 2
Solution
tomswons
Contributor

Based on i kinda figuret it out to get it work.
https://community.graphisoft.com/t5/Archicad-C-API/SOLVED-How-to-make-a-Palette-dialog-by-C-Class/m-...

Not sure if it is correct apporach becouse as far as i can see in DG_test menaging functionallity of buttons etc is diffrent then in this example above. Are there gonna be any troubles with this apporach in the future or can i stick with it?


 

 

Solution
Miklos Vegh
Graphisoft
Graphisoft

Here is a minimal implementation of a garbage collected palette. Using the DG_Test owner drawn listbox palette code as a template is not straightforward, as when the close button is pressed on it the palette instance will not be destroyed just get a hidden state and still continue to use system resources. With the following code the palette will be destroyed properly on a close interaction or call.

 

The resource looks like this:

'GDLG'  DGTEST_MINIMALPALETTE_RESID  Palette | grow | close	    0    0  200  100  "Minimal Palette Example" {
/* [  1] */ Button				  110   70   80   24	LargePlain  "Close"
/* [  2] */ LeftText			    4    4  192   60	LargePlain  StaticEdge  "This is a minimal DGPalette example dialog.\n\n"\
																				"The palette is visible only above the FloorPlan and the 3D Window."
}

'DLGH'  DGTEST_MINIMALPALETTE_RESID  Minimal_Palette_Example {
1	"Close Palette"				CloseButton
2	""							InfoText
}

The resource contains a static control and a button only. I implemented it in the DG_Test addon source so the palette ID follows the convention of that addon.

In order the palette to be dockable it has the resizeable flag.

 

In the code there are 2 objects. The first is a singleton manager object which is publicly available. The other is the palette object that is accessible only through the manager object, therefore both of its declartion and the definition is in the cpp file.

 

The header file (MinimalPalette.hpp):

// =====================================================================================================================
// MinimalPalette.hpp
// =====================================================================================================================

#if !defined (MINIMALPALETTE_HPP)
#define MINIMALPALETTE_HPP

#pragma once

#include "ACAPinc.h"
#include "APIEnvir.h"
#include "DGModule.hpp"
#include "DisposableObject.hpp"


class MinimalPalette;


// === class MinimalPaletteManager =====================================================================================

class MinimalPaletteManager: public GS::DisposeHandler
{
private:
	MinimalPalette* paletteInstance;

	MinimalPaletteManager ();

	MinimalPaletteManager (const MinimalPaletteManager& source) = delete;
	MinimalPaletteManager operator= (const MinimalPaletteManager& source) = delete;

	MinimalPalette& GetOrCreatePaletteInstance ();
	virtual void DisposeRequested (GS::DisposableObject& source) override;

public:
	virtual ~MinimalPaletteManager ();

	static const GS::Guid&	GetPaletteGuid ();
	static Int32			GetPaletteRefId ();

	static GSErrCode		PaletteAPIControlCallBack (Int32 referenceID, API_PaletteMessageID messageID, GS::IntPtr param);

	static MinimalPaletteManager& GetInstance ();

	bool	IsPaletteVisible () const;
	void	SetPaletteItemsStatus (bool enabled);

	void	ShowPalette ();
	void	ToggleVisibility ();
	void	ClosePalette ();
};

#endif // MINIMALPALETTE_HPP

 

The cpp file (MinimalPalette.cpp):

// =====================================================================================================================
// MinimalPalette.cpp
// =====================================================================================================================

#include "DGTestResIDs.hpp"
#include "DisposableObject.hpp"
#include "MinimalPalette.hpp"


// === MinimalPalette declaration=======================================================================================

class MinimalPalette: public DG::Palette,
					  public DG::PanelObserver,
					  public DG::ButtonItemObserver,
					  public GS::DisposableObject
{
private:
	enum {
		CloseButtonId	= 1,
		InfoTextId		= 2
	};

	DG::Button		closeButton;
	DG::LeftText	infoText;

	MinimalPalette ();

protected:
	virtual void	PanelOpened (const DG::PanelOpenEvent& ev) override;
	virtual	void	PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool* accepted) override;

	virtual void	ButtonClicked (const DG::ButtonClickEvent& ev) override;

public:
	MinimalPalette (const GS::Guid& paletteGuid);
	virtual ~MinimalPalette ();

	MinimalPalette (const MinimalPalette& source) = delete;
	MinimalPalette operator= (const MinimalPalette& source) = delete;

	void	Close ();
};


// === MinimalPaletteManager Implementation ============================================================================

MinimalPaletteManager::MinimalPaletteManager ():
	paletteInstance (nullptr)
{
}


MinimalPaletteManager::~MinimalPaletteManager ()
{
	PRECOND (paletteInstance == nullptr);
}


MinimalPalette& MinimalPaletteManager::GetOrCreatePaletteInstance ()
{
	if (paletteInstance == nullptr) {
		paletteInstance = new MinimalPalette (GetPaletteGuid ());
	}
	return *paletteInstance;
}


void MinimalPaletteManager::DisposeRequested (GS::DisposableObject& source)
{
	if (&source == paletteInstance) {
		paletteInstance = nullptr;
	}
}


const GS::Guid& MinimalPaletteManager::GetPaletteGuid ()
{
	static GS::Guid guid ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); // Please generate a unique guid here!
	return guid;
}


Int32 MinimalPaletteManager::GetPaletteRefId ()
{
	static Int32 refId (GS::CalculateHashValue (GetPaletteGuid ()));
	return refId;
}


GSErrCode MinimalPaletteManager::PaletteAPIControlCallBack (Int32 referenceID, API_PaletteMessageID messageID, GS::IntPtr param)
{
	static bool paletteOpenAtClose = false;
	GSErrCode err = NoError;
	if (referenceID == MinimalPaletteManager::GetPaletteRefId ()) {
		MinimalPaletteManager& paletteManager = MinimalPaletteManager::GetInstance ();

		switch (messageID) {
			case APIPalMsg_ClosePalette:
				paletteManager.ClosePalette ();
				break;

			case APIPalMsg_HidePalette_Begin:
				if (paletteManager.IsPaletteVisible ()) {
					paletteOpenAtClose = true;
					paletteManager.GetOrCreatePaletteInstance ().Hide ();
				} else {
					paletteOpenAtClose = false;
				}
				break;

			case APIPalMsg_OpenPalette:
				paletteOpenAtClose = true;
				[[fallthrough]];

			case APIPalMsg_HidePalette_End:
				if (paletteOpenAtClose && !paletteManager.IsPaletteVisible ()) {
					paletteManager.GetOrCreatePaletteInstance ().Show ();
				}
				break;

			case APIPalMsg_DisableItems_Begin:
				paletteManager.SetPaletteItemsStatus (false);
				break;

			case APIPalMsg_DisableItems_End:
				paletteManager.SetPaletteItemsStatus (true);
				break;

			case APIPalMsg_IsPaletteVisible:
				(*reinterpret_cast<bool*> (param)) = paletteManager.IsPaletteVisible ();
				break;

			default:
				break;
		}
	}

	return err;
}


MinimalPaletteManager& MinimalPaletteManager::GetInstance ()
{
	static MinimalPaletteManager managerSingletonInstance;

	return managerSingletonInstance;
}


bool MinimalPaletteManager::IsPaletteVisible () const
{
	return paletteInstance != nullptr && paletteInstance->IsVisible ();
}


void MinimalPaletteManager::SetPaletteItemsStatus (bool enabled)
{
	if (paletteInstance != nullptr) {
		if (enabled) {
			paletteInstance->EnableItems ();
		} else {
			paletteInstance->DisableItems ();
		}
	}
}


void MinimalPaletteManager::ShowPalette ()
{
	GetOrCreatePaletteInstance ().Show ();
}


void MinimalPaletteManager::ToggleVisibility ()
{
	if (IsPaletteVisible ()) {
		ClosePalette ();
	} else {
		ShowPalette ();
	}
}


void MinimalPaletteManager::ClosePalette ()
{
	if (paletteInstance != nullptr) {
		paletteInstance->Close ();
	}
}


// === MinimalPalette implementation ===================================================================================

MinimalPalette::MinimalPalette (const GS::Guid& paletteGuid):
	DG::Palette (ACAPI_GetOwnResModule (), DGTEST_MINIMALPALETTE_RESID, ACAPI_GetOwnResModule (), paletteGuid),
	closeButton (GetReference (), CloseButtonId),
	infoText (GetReference (), InfoTextId)
{
	SetDefaultGarbageCollector ();
	SetDisposeHandler (MinimalPaletteManager::GetInstance ());

	AutoResizeBlock (infoText.HVGrow, closeButton.HVMove);

	this->Attach (*this);
	closeButton.Attach (*this);

	this->BeginEventProcessing ();
}


MinimalPalette::~MinimalPalette ()
{
	this->Detach (*this);
	closeButton.Detach (*this);
}


void MinimalPalette::Close ()
{
	SendCloseRequest ();
}


void MinimalPalette::PanelOpened (const DG::PanelOpenEvent& /*ev*/)
{
//	if (ev.GetSource () != this) {
//		return;
//	}
//	SetClientSize (GetOriginalClientWidth (), GetOriginalClientHeight ()); // Restores the size defined in the resource
}


void MinimalPalette::PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool* /*accepted*/)
{
	if (ev.GetSource () != this) {
		return;
	}
	Hide ();
	EndEventProcessing ();
	MarkAsDisposable ();
}


void MinimalPalette::ButtonClicked (const DG::ButtonClickEvent& ev)
{
	if (ev.GetSource () == &closeButton) {
		Close ();
	}
}

 

Some details about how it works:

  • The manager object is descended from the DisposeHandler and implements its DisposeRequested function. DisposeRequested is called when the palette object is marked as disposable by calling MarkAsDisposable. From that time on the palette is treated as a non existing object although it will be deleted later in the event loop. The MarkAsDisposable is called when the user presses the close button.
  • Why do we need to use garbage collector? The palette object can not be deleted in its own PanelCloseRequested member function or in any other member function of itself, an other object is needed to keep track of the palette object pointer or reference.
  • The palette manager object provides interface to control palette. The palette directly is not accessible.
  • In the palette constructor the manager object is set as a dispose handler by calling SetDisposeHandler. That means the manager will be notified about the palette was marked as disposable so it can clear the palette pointer/reference. The default garbage collector is need to be set to the palette by SetDefaultGarbageCollector.
  • The MinimalPalette::Close member function is called through the manager object when the user wants to close the palette. This function calls the SendCloseRequest that results a PanelCloseRequested (this is called also when the X button is pressed in the palette caption).
  • In the PanelCloseRequested there are the Hide / EndEventProcessing / MarkAsDisposable calls. The order of them is important. Hide must be called to avoid the palette to get any events from that moment.
  • The garbage is destroyed in the AC event loop.

The palette can be registered and unregistered with these calls:

	ACAPI_RegisterModelessWindow (MinimalPaletteManager::GetPaletteRefId (),
								  MinimalPaletteManager::PaletteAPIControlCallBack,
								  API_PalEnabled_FloorPlan + API_PalEnabled_3D,
								  GSGuid2APIGuid (MinimalPaletteManager::GetPaletteGuid ()));

	ACAPI_UnregisterModelessWindow (MinimalPaletteManager::GetPaletteRefId ());

Here the palette is set to be visible only above the FloorPlan and the 3D Window.

You can open and close the palette with these functions:

	MinimalPaletteManager::GetInstance ().ShowPalette ();
	MinimalPaletteManager::GetInstance ().ClosePalette ();