I want to add a bitmap to a TMenuItem created dynamically. With this code it doesn't work, I don't have the image on my menu. What's wrong?
procedure TForm3.FormCreate(Sender: TObject);
var
item : TmenuItem;
icon : TIcon;
begin
item := TMenuItem.Create(PopupMenu1);
item.Caption := 'coucou';
icon := TIcon.Create;
icon.LoadFromFile('d:\SmallIcon.ico');
icon.Height := 16;
icon.Width := 16;
item.Bitmap.Canvas.Draw(0,0,icon);
PopupMenu1.Items.Add(item);
end;
The Bitmap property on TMenuItem isn't the way to go here. You really should use image lists instead. This will allow you to share images between your UI elements in a manageable fashion.
Add the icon to a TImageList.
Set the Images property on the menu (i.e. PopupMenu1) to refer to the image list.
Set the image index of the menu item to the index of the icon in the list, i.e. 0 if it's the first image.
Of course, you really ought to be using actions too, in which case you simply need to set the ImageIndex for the action and the framework takes care of assigning it to the menu item.
As an aside, I would note that the Delphi implementation of Vista themed menus has a large number of subtle bugs, many related to drawing of images. However, these bugs are relatively minor in visual impact.
Add the line
item.Bitmap.SetSize(16,16);
as third one. Then it works.
So your code would look like this:
var
item : TmenuItem;
icon : TIcon;
begin
item := TMenuItem.Create(PopupMenu1);
item.Caption := 'coucou';
item.Bitmap.SetSize(16,16); // <--- set size of bitmap
icon := TIcon.Create;
icon.LoadFromFile('d:\SmallIcon.ico');
icon.Height := 16;
icon.Width := 16;
item.Bitmap.Canvas.Draw(0,0,icon);
PopupMenu1.Items.Add(item);
end;
Although I agree with David. Better use a TImageList.
a) You can't set TIcon dimensions once they have an image in them -- if your loaded icon isn't already 16x16 you'll get an exception, b) You don't indicate if your parent menu uses a TImageList (if so, you can't set individual images), c) by default, I don't think tmenuitem bitmaps have a particular size/color depth or anything else. You need to properly create a TBitmap to assign to the TMenuItem.Bitmap (assuming your parent menu doesn't use TImageLists).
Related
If I programmatically change TComboBox.Images to a new TImageList, only the selected icon in the TComboBox changes, all other icons in the TComboBox (drop-down list) remain the same.
I have two TImageLists, one with color icons and one with black and white icons and I want to change the black and white icons to colored icons and vice versa.
procedure TfrmMain.Button1Click(Sender: TObject);
begin
if ComboBox1.Images = ImageList1 then
ComboBox1.Images := ImageList2
else
ComboBox1.Images := ImageList1;
end;
As pointed in the question comments this is indeed a bug in Delphi as the internal drop down list is not refreshed when changing the Images property after showing the popup at least one time. This can be corrected by making a small change in the FMX.ListBox.pas source file at TCustomComboBox.SetImages procedure:
For Delphi 11:
procedure TCustomComboBox.SetImages(const Value: TCustomImageList);
begin
FImageLink.Images := Value;
FItemsChanged := True; // <- Add this
end;
For Delphi 10.4 CE:
procedure TCustomComboBox.SetImages(const Value: TCustomImageList);
begin
FImageLink.Images := Value;
TComboBoxHelper.SetItemsChanged(Self, True); // <- Add this
end;
I'm rewriting a VCL component showing a customized TCustomListbox to Firemonkey in Delphi 10.2. The customization used an overridden DrawItem, basically adding some indentation and setting the text color depending on the item text and index.
DrawItem made it rather easy, but there seem to be nothing like that in FMX. I can override PaintChildren and draw every item myself, but then it looks differently and I have to deal with scrolling and everything myself. I'm just starting with FMX and don't have the sources yet.
Is there a DrawItem replacement in FMX? I may have missed it.
If not, how do it get the needed information? Basically, the rectangle to draw in and ideally the style used.
Problems
The solution by Hans works, but has some major problems:
Color
Setting the color doesn't work, the text is always black. I tried various possibilities including this one:
PROCEDURE TMyItem.Paint;
BEGIN
TextSettings.FontColor := TAlphaColorRec.Red;
INHERITED;
END;
Speed
Opening a box with 180 Items takes maybe two seconds. We need that many items and their count is actually the reason why we need a customized box (we provide filtering using the TEdit part of our component). A version using strings without TMyItem was faster (though probably slower than the VCL version), but using these items seems to slow it down even more (it's slower than filling an HTML list styled similarly).
Or something else? Having no sources and practically no documentation I can't tell.
I tried to cache the items for reuse, but this didn't help.
It looks like using custom items is actually faster than using strings, (timing in milliseconds):
nItems String TMyItem
200 672 12
2000 5604 267
20000 97322 18700
The speed problem seems to accumulate when the content changes multiple times. I was using FListBox.Items.Clear;, then I tried
n := FListBox.Items.Count;
FOR i := 0 TO n-1 DO FListBox.ListItems[n-1-i].Free;
and finally FListBox.Clear;, which makes most sense (and which I found last). Still, in the end it seems to need 2 ms per item.
Here is an example of how it can be done. The key is to set the Parent of the (custom) ListBoxItem to the ListBox. This will append it to its list of items. I set the parent in the constructor, so I don't have to do it (and remember it) each time I add something to a listbox.
type
tMyListBoxItem = class(TListBoxItem)
strict private
fTextLabel: TLabel;
public
constructor Create(aOwner: TComponent);
property TextLabel: TLabel read fTextLabel;
end;
implementation
constructor tMyListBoxItem.Create(aOwner: TComponent);
begin
inherited;
fTextLabel := TLabel.Create(self);
fTextLabel.Parent := self;
Assert(aOwner is TFMXObject, 'tMyListBoxItem.Create');
Parent := TFMXObject(aOwner);
end;
procedure tMyForm.FillListBox(aListBox: TListBox; aStringList: TStringList);
var
lItem: tMyListBoxItem;
i: integer;
begin
aListBox.BeginUpdate; //to avoid repainting for every item added
aListBox.Clear;
for i := 0 to aStringList.Count-1 do
begin
lItem := tMyListBoxItem.Create(aListBox);
lItem.TextLabel.Text := aStringList[i];
lItem.Margins.Left := 20;
end;
aListBox.EndUpdate;
end;
I use custom ListBoxItems in many places now because you can have ComboBoxes, EditBoxes, and all other controls in a ListboxItem. This opens for a very dynamic (list based) screen layout that easily adapts to all platforms and screen sizes.
Can you give me the names of the functions needed for this purpose? I'm using Delphi XE 5. I want to get this effect:
Window: half transparent
Font: fully visible.
I will use "System" font (zero problems with AA)
What do I look on MSDN? What functions (name) do I need to use?
This is basically the same idea as in Marcus' answer, but with some enhancements. You might have to adjust this to your needs, but the principle is the following:
Create form1 with the following properties:
AlphaBlend := True;
AlphaBlendValue := 128;
BorderStyle := bsNone;
Create form2 with the controls as desired and the following properties:
Color := clFuchsia; // or whatever color is not used
TransparentColor := true;
TransparentColorValue := Color;
Declare a Boolean field in form1 named AllowMove.
In TForm1.FormShow call the following code:
begin
form2.BorderStyle := bsNone;
form2.SetBounds(0, 0, ClientWidth, ClientHeight);
form2.Show;
AllowMove := true;
end;
Declare a Boolean field in form1 named AllowMove and a message handler for WM_MOVE:
procedure WMMOVE(var Message: TMessage); message WM_MOVE;
procedure TForm1.WMMOVE(var Message: TMessage);
begin
inherited;
if AllowMove then begin
Form2.Left := Message.LParamLo;
Form2.Top := Message.LParamHi;
end;
Message.Result := 0;
end;
The only way that I know to get that kind of effect is to render the window contents to an in-memory bitmap, then apply the desired alpha values to the non-font pixels, and then use UpdateLayeredWindow() to display the bitmap on a window. You cannot achieve that affect with a TForm as it relies on SetLayeredWindowAttributes() instead.
Create a 32bit bitmap and draw the desired background on it with alpha values, using a separate array to keep track of the current pixel values in the spots you are going to draw text on, then draw the actual text and use the array to detect which pixels were changed so you can clear the alpha values from just those pixels. Then display the bitmap.
You can get something close by layering two forms over each other. Set the bottom form's color to blue, enable AlphaBlend, and set AlphaBlend to something like 100. That just provides the blue background.
On second form, set TransparentColor to clBtnFace, and put your label there. Set the label font's quality to fqAntialiased.
Set both form's BorderStyle to bsNone.
Lay the second form over the first form, and there you go.
This might be workable if you don't plan on letting the user move the forms, or you move them together.
I've spended hours for this (simple) one and don't find a solution :/
I'm using D7 and the TImageList. The ImageList is assigned to a toolbar.
When I populate the ImageList at designtime, the icons (with partial transparency) are looking fine.
But I need to populate it at runtime, and when I do this the icons are looking pretty shitty - complete loose of the partial transparency.
I just tried to load the icons from a .res file - with the same result.
I've tried third party image lists also without success.
I have no clue what I could do :/
Thanks 2 all ;)
edit:
To be honest I dont know exactly whats going on. Alpha blending is the correkt term...
Here are 2 screenies:
Icon added at designtime:
(source: shs-it.de)
Icon added at runtime:
(source: shs-it.de)
Your comment that alpha blending is not supported just brought the solution:
I've edited the image in an editor and removed the "alpha blended" pixels - and now it looks fine.
But its still strange that the icons look other when added at runtime instead of designtime. If you (or somebody else ;) can explain it, I would be happy ;)
thanks for you support!
To support alpha transparency, you need to create the image list and populate it at runtime:
function AddIconFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Icon.LoadFromResourceID(HInstance, ResID);
Result := ImageList.AddIcon(Icon);
finally
Icon.Free;
end;
end;
function AddPngFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Png: TPngGraphic;
ResStream: TStream;
Bitmap: TBitmap;
begin
ResStream := nil;
Png := nil;
Bitmap := nil;
try
ResStream := TResourceStream.CreateFromID(HInstance, ResID, RT_RCDATA);
Png := TPNGGraphic.Create;
Png.LoadFromStream(ResStream);
FreeAndNil(ResStream);
Bitmap := TBitmap.Create;
Bitmap.Assign(Png);
FreeAndNil(Png);
Result := ImageList.Add(Bitmap, nil);
finally
Bitmap.Free;
ResStream.Free;
Png.Free;
end;
end;
// this could be e.g. in the form's or datamodule's OnCreate event
begin
// create the imagelist
ImageList := TImageList.Create(Self);
ImageList.Name := 'ImageList';
ImageList.DrawingStyle := dsTransparent;
ImageList.Handle := ImageList_Create(ImageList.Width, ImageList.Height, ILC_COLOR32 or ILC_MASK, 0, ImageList.AllocBy);
// populate the imagelist with png images from resources
AddPngFromResource(ImageList, ...);
// or icons
AddIconFromResource(ImageList, ...);
end;
I had the exact same problems a couple of years ago. It's a Delphi problem. I ended up putting the images in the list at design time, even though I really didn't want to. I also had to use a DevExpress image list to get the best results and to use 32 bit color images.
As Jeremy said this is indeed a Delphi limitation.
One work around I've used for images that I was putting onto buttons (PNGs with alpha transparency in my case) is to store the PNGs as resources, and at run time paint them onto a button sized bitmap filled with clBtnFace. The bitmap was then used as the control's glyph.
Delphi's built in support for icons with alpha masks is very limited, however there's an excellent icon library kicon which may help.
I have a report that uses a TChart that I am maintaining. One of the TLineSeries that gets added automatically gets assigned the color clWhite, which is too close to the background (clBtnFace).
If I change it, then the next series that gets added takes clWhite. So short of going back and changing it after all the other series are created, is there some way to tell the TChart that I don't want any of my series to be clWhite?
When a series is added to the TChart the TChart assigns it a color. I want it to not assign clWhite.
OK not one to give up easily, I did some more searching. There is a unit variable called ColorPalette of type TColorArray in the TeeProcs unit. If I find and replace white with a different color that fixes it. There may be an instance copy of it. I'll keep looking since that would be preferred.
To revert the ColorPalette back just call the unit method SetDefaultColorPalette in the same unit.
SetDefaultColorPalette; // Make sure we start with the default
ColorPalette[4] := $007FFF; // Change White to Orange
try
// add series to the chart
finally
SetDefaultColorPalette; // Set it back to Default
end;
BTW, I can't accept as answer because I asked the question too, but I tested it and it works.
Near as I can tell from the TeeCharts module; no you can't specify a color that it should not be as it ships.
You can programatically walk through all the TLineSeries entries making sure at run-time that they don't use clWhite.
Say you have an array of acceptable colors clArray, you can use the following code to set the colors of each of the tLineSeries entries at run time.
procedure TForm1.setColors(aChart: TChart; aColorArray: array of TColor);
var
chi : Integer;
coi : Integer;
begin
coi := low(aColorArray);
for chi := 0 to aChart.SeriesList.Count - 1 do begin
aChart.SeriesList[chi].Color := aColorArray[coi];
inc(coi);
if coi > high(aColorArray) then
coi := low(aColorArray);
end;
end;
procedure TForm1.FormShow(Sender: TObject);
var
ca : array of TColor;
begin
setLength(ca, 3);
ca[0] := clRed;
ca[1] := clBlue;
ca[2] := clGreen;
setColors(Chart1, ca);
end;
You can use the series methods ClearPalette then AddPalette to create your custom palette.