Creating a ComboBox with one or more separator items? - delphi

I'm using Delphi7 and I'd like to have a ComboBox with separator items (Just like in popup menus).
I've seen this beautifully implemented in Mozilla Sunbird (I know, it's not Delphi...) the following way:
The separator item is a simple gray line
drawn in the center of the item
If you hover over the separator with
the mouse, the selection doesn't
appear
If the user clicks the separator,
it's not selected either AND the
combobox doesn't closeup.
No. 1 could be implemented using DrawItem. I could live without No. 2 because I have no idea about that.
For No. 3 I'm asking for your help. I've figured out that straight after closing up a CBN_CLOSEUP message is sent to the combobox.
I thought about hooking the window proc and if CBN_CLOSEUP is sent to a certain combobox then countering it. But I'm unsure if this is the best solution, or maybe there are other, more elegant ways?
Whatever the solution is, I'd like to have a standard ComboBox which supports WinXP/Vista/7 theming properly.
Thanks!
Edit: For a working component please see this thread:
Can you help translating this very small C++ component to Delphi?

I played around with making unclickable separator items (as described in this answer) and ran into several UI glitches. The problem is that combo boxes have several aspects to their behavior that can be hard to get exactly right:
Pressing the up and down arrow keys navigates the list while the list is dropped down.
Pressing Enter closes the dropped down list, selecting the current item.
Pressing Escape closes the dropped down list, selecting the current item (if the current item was chosen with the up and down arrow keys) or the last selected item.
If the combo box has the focus, then pressing the up and down arrow keys to changes the current selection without displaying the list.
If the combo box has the focus, then typing anything selects the combo box item matching whatever is typing.
If the combo box has the focus, then pressing F4 drops down the combo box list, which can then be controlled by keyboard or mouse.
Ensuring that disabled separator items don't respond to any of these events (plus any other events which I may be missing, e.g., screen readers?) seems fraught with error.
Instead, the approach I'm using is to draw the separator as part of the item:
Use a variable height owner draw combo box.
Add 3 pixels to the height for any items that need separators.
Draw a horizontal line at the top of each item needing a separator.
Here's some C++Builder code to accomplish this; translating it to Delphi should be easy enough.
void __fastcall TForm1::ComboBox1DrawItem(TWinControl *Control,
int Index, TRect &Rect, TOwnerDrawState State)
{
bool draw_separator = NeedsSeparator(Index) &&
!State.Contains(odComboBoxEdit);
TCanvas *canvas = dynamic_cast<TCustomCombo*>(Control)->Canvas;
canvas->FillRect(Rect);
TRect text_rect = Rect;
// Add space for separator if needed.
if (draw_separator) {
text_rect.Top += 3;
}
canvas->TextOut(text_rect.Left + 3,
(text_rect.Top + text_rect.Bottom) / 2 -
canvas->TextHeight(ComboBox1->Items->Strings[Index]) / 2),
ComboBox1->Items->Strings[Index]);
// Draw a separator line above the item if needed.
if (draw_separator) {
canvas->Pen->Color = canvas->Font->Color;
canvas->MoveTo(Rect.Left, Rect.Top + 1);
canvas->LineTo(Rect.Right, Rect.Top + 1);
}
}
void __fastcall TForm1::ComboBox1MeasureItem(
TWinControl * /* Control */, int Index, int &Height)
{
Height = ComboBox1->ItemHeight;
// Add space for the separator if needed.
if (Index != -1 && NeedsSeparator(Index)) {
Height += 3;
}
}

What you want is an owner-drawn combobox. See this: http://delphi.about.com/od/vclusing/a/drawincombobox.htm
Also, this seems to solve making the item unclicable:
http://borland.newsgroups.archived.at/public.delphi.vcl.components.using.win32/200708/0708225320.html
As far as I know there is no VCL way of doing that, so you'll have to subclass the combobox. It would be nice to create component encapsulating those functionalities so you can reuse them easily.
God bless

If you want your controls to look good use the free SpTBXLib. It supports combo style components which popup a popup menu with lines.

Related

Scroll bar in LibreOffice dialog

I am trying to make an image picker component in LibreOffice.
I have a dialog that is dynamically filled with images. When the user clicks on one images, it should be selected and the dialog should be closed.
The problem is that the number of images is variable. So I need to enable scrolling in the dialog (so that the user can navigate through all images).
There seems to be some properties on the dialog object (Scrollbars, Scroll width, Scroll height, etc)
However, I cannot find a way to use them anywhere.
Any ideas?
The scrollbar is one of the Controls available through the dialog box editor. That is the easier way to put a ScrollBar on a dialog box. Just insert it like any other control. There is a harder way via DialogModel.addControl but that seems non-essential to answering this question.
If you add a scrollbar to the dialog box and run the dialog box, you will find it does nothing by default. The functionality (apparently) must be written into a macro. The appropriate triggering event is the While Adjusting event on the ScrollBar object, although it does not trigger the macro simply with the "Test Mode" function in the dialog editor. Running the dialog box through a macro triggers the While Adjusting event when the scroll arrows are triggered, when the slider area is clicked to move the slider, and when the slider itself is dragged. The Object variable returned by the scrollbar event contains a property .Value which is an absolute value between 0 and the EventObject.Model.ScrollValueMax, which allows you to manipulate the other objects on the page manually based on the position of the slider.
Yes, that's right, manipulate objects manually. The sole example I found, from the LibreOffice 4.5 SDK, does precisely this. Of course, it is not as bad as it sounds, because one can iterate through all of the objects on the page by reading the array Dialog.getControls(). In any event, the secret sauce of the example provided in the SDK is to define Static variables to save the initial positions of all of the objects you manipulate with the scrollbar and then simply index those initial positions based on a ratio derived from the scrollbar Value divided by the ScrollValueMax.
Here is a very simple working example of how to scroll. This requires a saved Dialog1 in the Standard library of your document, which contains an object ScrollBar1 (a vertical scrollbar) and Label1 anywhere in the dialog. The ScrollBar1 must be configured to execute the macro ScrBar subroutine (below) on the While Adjusting event. Open the dialog by executing the OpenDialog macro and the scrollbar will move the Label1 control up and down in proportion to the page.
Sub OpenDialog
DialogLibraries.LoadLibrary("Standard")
oVariable = DialogLibraries.Standard.Dialog1
oDialog1 = CreateUnoDialog( oVariable )
oDialog1.Execute()
End Sub
Sub ScrBar (oEventObj As Object)
Static bInit As Boolean
Static PositionLbl1Y0 As Long
oSrc = oEventObj.Source
oSrcModel = oSrc.Model
scrollRatio = oEventObj.Value / oSrcModel.ScrollValueMax
oContx = oSrc.Context
oContxModl = oContx.Model
oLbl1 = oContx.getControl("Label1")
oLbl1Model = oLbl1.Model
REM on initialization remember the position of the label
If bInit = False Then
bInit = True
PositionLbl1Y0 = oLbl1Model.PositionY
End If
oLbl1Model.PositionY = PositionLbl1Y0 - (scrollRatio * oContx.Size.Height)
End Sub
The example provided by the SDK does not run on my setup, but the principles are sound.
There appears to be a second improvised method closer to the functionality one might expect. This method uses the DialogModel.scrollTop property. The property appears to iterate the entire box up or down as a scroll based on the user input. There are two problems using this methodology, however. First, unless you put the scrollbar somewhere else, the scroll bar will scroll away along with the rest of the page. You will need to adjust the location of the scrollbar precisely to compensate for/negate the scrolling of the entire page. In the example below I tried but did not perfect this. Second, the property seems to miss inputs with frequency and easily goes out of alignment/ enters a maladjusted state. Perhaps you can overcome these limitations. Here is the example, relying on the same setup described above.
Sub ScrBar (oEventObj As Object)
Static scrollPos
oSrc = oEventObj.Source
oSrcModel = oSrc.Model
scrollRatio = oEventObj.Value / oSrcModel.ScrollValueMax
If IsEmpty(scrollPos) = False Then
scrollDiff = oEventObj.Value - scrollPos
Else
scrollDiff = oEventObj.Value
End If
scrollPos = oEventObj.Value
oContx = oSrc.Context
oContxModl = oContx.Model
oContxModl.scrollTop = scrollDiff * -1
oSrcModel.PositionY=(scrollRatio * oContx.Size.Height/5) * -1
End Sub
This (sort of) will scroll the contents of the entire dialog box, within limits and with the caveats noted above.

Advancing control focus in a VCL TFrame

I have a TFrame on which some TEdits are placed. These edits are
boxes for serial key input, as I'm trying to setup a user experience
where input focus jumps from one edit box to the next, when a certain amount of
characters been entered in each. That is, user do not need to press tab
or click on the next edit in order to advance.
I found an example in the C++ Builder HowTo book (great book) on how to
"simulate" enter press to behave like a tab press in edits and was
trying to employ the same technique. However, something in my app don't
work as in that example.
In the frames KeyPress event, I have the code
void __fastcall TAboutFrame::Edit1KeyPress(TObject *Sender,
System::WideChar &Key)
{
TEdit* theEdit = dynamic_cast<TEdit*>(Sender);
if(!theEdit)
{
return;
}
if(theEdit->Text.Length() >= 6)
{
//jump to next edit
Perform(WM_NEXTDLGCTL, 0, 0);
...
But the 'jump' to next control does not occur.
The main Form, the frames parent, do have key preview == true and I can
set a breakpoint to see that the Perform call is indeed executed.
The tab orders on the edits are 1,2,3,4,5.
I wonder if this has todo with TFrames messaging or?
If the controls you are using descend from TWinControl (Which they should if you are using stock VCL controls), You can also use TWinControl->SetFocus() to explicitly set the focus to the desired control.

Positioning of custom list box item components in Delphi XE5, Firemonkey

I've customised the style of a Firmeonkey list box item in such a way that now it can consist of 4 TLables in it. Each of the lable has Alignment as alNone.
I'm setting position of each of them in my code whenever i need to add any item. I've observed that when my list has scroll bar and if first component is not visible (i.e. i've scrolled down enough) at that time if i re-add all the items again in list box, then the position of TLabels in first items (or items which are not shown) get distorted.
For setting positions I am using below code :
(tmpListBoxItem.FindStyleResource('txtCol2') As TLabel).Position.X :=
(tmpListBoxItem.FindStyleResource('txtCol2') As TLabel).Position.X + (tmpListBoxItem.FindStyleResource('txtCol2') As TLabel).Width;
Any suggesstions, how can i overcome this issue.
Regards,
Padam Jain
Firemonkey styles are repeatedly 'applied' and 'freed' as components appear and disappear from screen.
It is not enough to simply set properties of style objects once and expect those values to be remembered. What you need to do is either listen to the OnApplyStyleLookup event or override the ApplyStyle method of a custom component and use the same you have above to set the properties again.
This means you'll need somewhere to store the values you are going to set.
I would suggest for your situation that you subclass TListBoxItem so you can add suitable properties or fields and put your code in ApplyStyle.

c++ builder MDI / SDI or other approach?

i wanna make a windows database application with c++ builder. The idea is to have a static menu of 6 icons at the top (i need this to be constant in every screen) while the rest of the screen will host all user interactions and data regarding with the selected menu item. I have a litte experiece with SDI apps and as far as i know there is no way the whole application to be in a single screen / form. Should i build this like an MDI app or is there any other way to maintain a fixed icon based menu at top while the rest screen data to change for every different menu item? I dont want to be in a single window with no overlaping forms while user navigates through the application.
Although an MDI application is definitely possible, the interaction between the different forms sometimes is a little cumbersome. A tabbed page is easier to handle since everything then resides within the same TForm class.
If you want to change the appearance of the individual tabs you can overload the 'PageControlDrawTab' Just add an event handler, get a handle to the Canvas of the tab itself and you are free to draw is as you want. See the example below:
void __fastcall TMainForm::PageControlDrawTab(TCustomTabControl *Control,
int TabIndex, const TRect &Rect, bool Active)
{
/* OnDraw handler to change the appearance of the Tabs.
Change it to blue text on white background.
*/
String s;
TRect r;
TTabControl * tTab = (TTabControl *)Control; // Get a pointer to the tab itself
s = tTab->Tabs->Strings[TabIndex]; // Retrieve the text of this tab
Control->Canvas->Brush->Color = clWhite; // Use the Canvas to draw
Control->Canvas->Font->Color = clBlue; // .. whatever you like
Control->Canvas->FillRect(Rect);
Control->Canvas->TextRect(Rect,Rect.Left+4,Rect.Top+2,s);
}
You will probably have to do it in a MDI format. I do not know of any way to share the menu across forms. The other option you could use though is to use a page control and have all the other "forms" live in a tab so the menu is the same all the time. The menu items could respond differently if you would like them to when the user is on a different tab, or they could do the same thing no matter what tab you are on. Sorry this is the form of an answer, I don't have comment rights yet.

How do you rename a Status Bar Panel in Delphi 2010

I have just added a new panel to StatusBar1 and it is called 5 - TStatusPanel. I want to give it a different name but I can't remember how to do this.
I want to rename 5 - TStatusPanel to 5 - GripArea. As you can see from the image I have done this before (see Num, Caps, AM/PM) but I can't remember how I did this. It sucks to get old.
Just change the Text property of the TStatusPanel. This is what is displayed in the status panel editor. Of course, this will make the text visible in the panel! Normally, in code, you access the status panels using the StatusBar1.Panels[PanelIndex] array. PanelIndex is the zero-based index of the panel. I always declare constants such as
STATUS_FILE_POSITION = 0;
STATUS_FILE_SAVED = 1;
STATUS_LONG_TEXT = 2;
STATUS_ZOOM_CONTROL = 3;
and use these to remember the panels. (The code above is from my text editor.)
So I can do, for instance,
StatusBar.Panels[STATUS_FILE_SAVED].Text := 'Modified';
Here's a tip that would have saved some hunting: right-click on the form, View As Text. Now you'll see the form layed out as properties, and you could have found the control, seen how the other panels were named, and fix the last one.
Alt+F12 to toggle the text view on/off.

Resources