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.
Related
I have a ModalBottomSheetLayout with a list of items in my Compose view, which can be showed by some UI interaction.
By default, when bottomSheetState.show() is called, the visible ratio for the BottomSheet is 50%. However, this is not ideal from a UX perspective as the user will have to physically pull up the bottom sheet to see all the contents in the list.
Extremely frustrating is the fact that bottomSheetState.show() does not take in any parameters, and that the 50% value seems to be hard coded in. According to the declaration in androix.compose.material:
suspend fun show() {
val targetValue =
if (isHalfExpandedEnabled) HalfExpanded
else Expanded
animateTo(targetValue = targetValue)
}
I would like to instead show a custom value, say 75%, when the bottom sheet is showed, but so far I haven't found a way to do so. Is there a workaround to this?
I am not sure if you can make it 75% visible but you can show it expanded
bottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
What is the correct procedure for Scaling a TFrame in C++Builder?
I am developing in PixelsPerInch=120 (aka. Windows font size 125%), but want my forms to work in PixelsPerInch=96 also (aka. default).
My main form loads fine: the VCL code to create a TForm includes a check of whether the design PPI differs from runtime PPI and it scales everything correctly at that point.
However when loading a TFrame, no automatic scaling occurs. So far as I have been able to discover, I have to manually call Frame1->ScaleBy(M, D);.
This mostly works however it has a bug (which I will describe below) so the result is a frame that is not scaled correctly.
What is the proper way to create a Frame and have it scale on load, like a Form does?
I am currently using the following:
// class member
TFrame *f;
// called just before Application->Run()
f = new TFrame(fMain);
int M = fMain->PixelsPerInch;
int D = 120; // Matt's development
if ( M != D )
{
f->ScaleBy(M, D);
}
and then when I want to activate the frame, `f->Parent = fMain->Panel1;
(*f)->ScaleBy(M, D);
I have also tried writing f->Parent = fMain->Panel1; before calling ScaleBy, based on the theory that the Frame has controls using ParentFont and so on, so it might be better if this info is available during scaling; however this introduces other bugs.
The bug is that when calling ScaleBy on a Frame or a Form which has a TLabel which has AutoSize=true and ParentFont=false and Anchors including akBottom, then the Label is displayed much higher up on the window than it should be (maybe even off the top of the window).
I have tracked down the problem: the TControl.ChangeScale function includes a call to ScaleMargins, and the Margin property has an on-set trigger that ends up calling AlignControls for the parent form, which will move any controls with AutoSize=true.
By stepping through Vcl.Controls.pas, I see that my Label scales correctly at first , however when the next control (a radio group, it happens to be) is processed, the AlignControls is triggered, which changes my Label's Top.
The AlignControls function apparently can't handle the TLabel with the parameters I just described when called part-way through a rescaling operation.
I haven't firmly nailed down why, but I think it is because the parent Form or Frame has not yet been scaled (the ScaleControls function scales all the children before scaling the self), so it uses the form's old Height to calculate the actual Top that results from doing alignment to the bottom.
This effect is not triggered when loading the Form the first time, because the scaling code checks for csLoading component state and disables a whole lot of reactionary effects, including the call to AlignControls.
I haven't figured out why the bug is only triggered when ParentFont=false.
My current workaround is to include a line in the FormResize handler that manually realigns the Labels in question with another label that has ParentFont=true (and thus doesn't suffer from the bug).
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.
I created a dynamic form in Adobe LiveCycle. I have buttons inside my content to add zones dynamically.
The problem is I would like to hide these buttons when the form is printed. I can do this with the Visible (Screen Only) option, but objects still occupies space in the form layout. Is there a solution to completely remove objects on printing (make them invisible without occupying space).
Thank you!
You can use the prePrint event to remove them from the layout. Also postPrint event to put them back in. Assuming that you are using flowed layouts, the objects won't occupy any space.
You just need to use
this.presence = "hidden";
to hide the button (on the prePrint event of the button).
this.presence = "visible";
to show the button (on the postPrint event)
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.