We value your input! Please participate in Archicad 28 Home Screen and Tooltips/Quick Tutorials survey
2023-04-13 11:39 PM
Hello Everyone!
I'm trying to make a dialog with a SingleSelListView that shall list Favorites to choose from. If I'm launching the add-on from floor plan view the preview images of the favorites are visible. When I'm launching the add-on from a section view the preview images are blank white. What do I do wrong?
Here's the function that I'm trying to use to set the list items (it's still experimental just sets the item texts to "elem i" and uses the same label preview image to all items):
void FillSingleSelListView(DG::SingleSelListView& listView, short elemNum)
{
for (short i = 1; i <= elemNum; i++)
{
listView.AppendItem();
listView.SetItemText(i, "elem " + GS::ValueToUniString(i));
short listImageWidth = 0;
short listImageHeight = 0;
listView.GetImageSize(&listImageWidth, &listImageHeight);
NewDisplay::NativeImage img(listImageWidth, listImageHeight, 32, nullptr, false, 0, true, 1.0);
GSErrCode err = ACAPI_Favorite_GetPreviewImage("Zone Label 01", APIImage_Model2D, &img);
if (err == NoError)
{
listView.SetItemColor(i, Gfx::Color::Red);
void* image = GX::Image(img).ToDGPicture();
DG::Image itemImage(image);
listView.SetItemImage(i, DG::ListView::ImageType::Picture, itemImage);
}
}
}
When launched from floor plan or 3D view I get this:
When launched from section or elevation view it looks like this:
Can anyone give some advice what to do differently? Any help is appreciated!
2023-04-17 12:43 PM
I think there is a problem with freeing the created image. When you call the ToDGPicture then a bitmap is created which should be destroyed by the dialog otherwise memory leak happens.
The DG::Picture data should be destroyed with platform dependent functions (after the listview item is removed or the dialog is closed). On windows: with
DeleteObject (reinterpret_cast<HGDIOBJ> (const_cast<void*> (image)))
and on mac with
DG::ReleaseNativeImage (const_cast<void*> (image));
It is important that you keep this image while it is displayed on a listview item.
Instead of using DG::Image (this is a base class) please use DG::Picture or DG::Icon.
You preferably use DG::Icon type images, since these are simpler to destroy, and can have transparency info also.
Here is an example using Icon type image. To have the DG::Icon object be available during the the image is set on the listview, it is implemented as a dialog member variable (it could be a heap object also).
class LVDialog
{
DG::Icon iconImage; //We use icon type instead of pict
...
};
LVDialog::LoadImage()
{
double resolutionFactor = listView.GetResolutionFactor ();
NewDisplay::NativeImage img(listImageWidth, listImageHeight, 32, nullptr, false, 0, true, resolutionFactor);
GSErrCode err = ACAPI_Favorite_GetPreviewImage("Zone Label 01", APIImage_Model2D, &img);
if (err == NoError)
{
listView.SetItemColor(i, Gfx::Color::Red);
// Destroy the previous image to avoid leak on scale change (HDPI handling)
if (iconImage.GetData () != nullptr) {
DG::Utils::DestroyDGIcon (iconImage);
}
iconImage = DG::Icon(GX::Image(img).ToDGIcon()); // ToDGIcon! -- Creates a HICON on windows
listView.SetItemImage(i, DG::ListView::ImageType::Icon, iconImage); // DG::ListView::ImageType::Icon!
}
}
LVDialog::~LVDialog ()
{
DG::Utils::DestroyDGIcon (iconImage); // Destroys HICON (or the mac image)
}
The example has a little addition for HDPI handling. The last parameter of the NativeImage is the resolution factor. The image should be regenerated whenever the resolution factor changes -> see DG::PanelObserver::PanelScaleChanged (const PanelScaleChangeEvent& ev).
This hopefully resolves the leak problem. If the preview generation on section windows has an issue that may be a different problem.
2023-04-19 12:23 AM
Hi Miklos!
I really appreciate your answer and thank you for widening my knowledge of image manipulation. I insert here the new code I wrote based upon your suggestion.
The dialog header file:
class MainDialog : public DG::ModalDialog,
public DG::PanelObserver,
public DG::ButtonItemObserver,
public DG::ListViewObserver,
public DG::CompoundItemObserver
{
public:
enum DialogResourceIds
{
ExampleDialogResourceId = ID_ADDON_MAIN_DLG,
OKButtonId = 1,
CancelButtonId = 2,
ListViewId = 3,
SeparatorId = 4
};
MainDialog();
~MainDialog();
private:
virtual void ButtonClicked(const DG::ButtonClickEvent& ev) override;
void FillListItemView();
DG::Button okButton;
DG::Button cancelButton;
DG::Separator separator;
DG::SingleSelListView listView;
GS::Array<DG::Icon*> listItemIcons;
};
And the dialog cpp file:
MainDialog::MainDialog() :
DG::ModalDialog(ACAPI_GetOwnResModule(), ExampleDialogResourceId, ACAPI_GetOwnResModule()),
okButton(GetReference(), OKButtonId),
cancelButton(GetReference(), CancelButtonId),
separator(GetReference(), SeparatorId),
listView(GetReference(), ListViewId)
{
AttachToAllItems(*this);
Attach(*this);
FillListItemView();
}
MainDialog::~MainDialog()
{
Detach(*this);
DetachFromAllItems(*this);
for (auto icon : listItemIcons)
{
DG::Utils::DestroyDGIcon(*icon);
delete icon;
}
}
void MainDialog::ButtonClicked(const DG::ButtonClickEvent& ev)
{
if (ev.GetSource() == &okButton) {
PostCloseRequest(DG::ModalDialog::Accept);
}
else if (ev.GetSource() == &cancelButton) {
PostCloseRequest(DG::ModalDialog::Cancel);
}
}
void MainDialog::FillListItemView()
{
GSErrCode err = NoError;
GS::Array<GS::UniString> names;
err = ACAPI_Favorite_GetNum(API_WallID, APIVarId_Generic, nullptr, nullptr, &names);
if (err == NoError)
{
double resFact = this->listView.GetResolutionFactor();
short imageWidth = 0;
short imageHeight = 0;
short gap = 10;
this->listView.GetImageSize(&imageWidth, &imageHeight);
NewDisplay::NativeImage img(imageWidth, imageHeight, 32, nullptr, false, 0, true, resFact);
for (auto name : names)
{
err = ACAPI_Favorite_GetPreviewImage(name, APIImage_Model2D, &img);
if (err == NoError)
{
DG::Icon* icon = new DG::Icon(GX::Image(img).ToDGIcon());
listItemIcons.Push(icon);
}
}
for (short i = 1; i <= short(names.GetSize()); i++)
{
listView.AppendItem();
listView.SetItemText(i, names[i - 1]);
listView.SetImageGap(gap);
listView.SetItemWidth((listView.GetWidth() - (3 * gap)) / 2);
listView.SetItemHeight(short(listView.GetItemWidth() * 0.8));
listView.SetItemColor(i, Gfx::Color::Red);
listView.SetItemImage(i, DG::ListView::ImageType::Icon, (*listItemIcons[i - 1]));
}
}
}
It works as before. If I start the add-on from a floor plan view the dialog appears like this:
The item previews are visible in the dialog.
But if I start from a section view it looks like this:
The list view appears with the right item texts but without preview images. What am I doing still wrong? It is suspicious that the item icons are white, so it seems to me that there's a preview image but it is not shown in the correct way.
Starting from a 3D view works as floor plan view:
Do you have any suggestion? (I'm using the AC25 devkit.)
2023-04-20 12:46 PM
Do the ACAPI_Favorite_GetNum and ACAPI_Favorite_GetPreviewImage return with NoError?
2023-04-21 10:48 PM
Hi Miklos!
Thank you again for your reply!
Yes, they do return with 0 (NoError).
As I wrote before, what I find interesting is that the list item images are blank white when the add-on is started while a section view is active.
If I don't use the previewimages and use just Icons created with the default constructor the list item images are grey squares, no matter from which view I start the add-on.
DG::Icon* icon = new DG::Icon();
listItemIcons.Push(icon);
This makes me think that I'm still doing something wrong. Could the active view have any effect on the functions I try to use?