Changing background color of a listbox's cell - xojo

Working on a desktop application and having a probably silly problem that's still managing to driving me mad.
I have a listbox that gets its rows from a dynamic source.
I'm trying to change the forecolor of specific cells in the control if the underlying data for that cell changes to specific conditions.
The control also gets drawn once before the condition starts being checked,meaning every cells starts painted with the same (white) forecolor.
Also, this listbox sits inside a Canvas, and that Canvas is in turn inside a Window control.
This code checks for the condition vs. all the rows in the listbox:
for each dict as Dictionary in WidgetsDictionary
Dim site as String = dict.Value("Site").StringValue
Dim device as String = dict.Value("Device").StringValue
Dim sensor as String = dict.Value("Sensor").StringValue
for intC as integer = 0 to actualLstBox.ListCount
Dim siteComp as String = actualLstBox.Cell(intC,4)
Dim deviceComp as String = actualLstBox.Cell(intC,0)
Dim sensorComp as String = actualLstBox.Cell(intC,1)
if actualLstBox.Cell(intC,4) = site AND
actualLstBox.Cell(intC,1) = sensor AND
actualLstBox.Cell(intC,0) = device then
actualLstBox.CellTag(intC,2) = RGB(255, 192, 203)
exit For
end
next
next
Where WidgetsDictionary contains the conditions I need to check against.
And that works,if i check the CellTags after it runs I find them set correctly where they are supposed to be.
Now,if after that code i call
actualLstBox.Refresh()
Note: I know Refresh isn't optimal but I need it to fire as soon as possible
I see the code jumping to the CellBackgroundPaint event of the ListBox
There I have this
If (row<me.ListCount ) then
If Me.CellTag(row, column ) <>nil Then
g.ForeColor = me.CellTag(row,column)
g.FillRect(0, 0, g.Width, g.Height)
End If
end
And again, I see this code executing correctly.
So, I'd expect the list to be redrawn with the correct cells in the new color.
But nothing changes, I see the CellBackgroundPaint event fire after every refresh but the end result shown is always the default (white) colored cells.
I've tried calling, in order
InvalidateCell on the specific cell
Invalidate on the whole Listbox instead of Refresh (since you never know)
Refresh on the containing Canvas
After the first block of code,to no avail.
So now I'm at a loss on what to try next.
Edit
If i replace the event handler with
If (row mod 2) = 0 Then
g.ForeColor = RGB(232,235,255)
g.FillRect 0, 0, g.Width, g.Height
End If
if column = 2 then
g.ForeColor = RGB(255,253,208)
g.FillRect 0, 0, g.Width, g.Height
end
I get alternating colors for the rows and the whole 3rd column of a different colour.
So I guess the fact that I'm trying to repaint it after the first time it's shown is where the problem lies.

You need to return true from the handler in order to tell Xojo that you already have do e the painting. Otherwise Xojo will do its own FillRect after yours.
Keep in mind that this will also override the selection coloring. Meaning that if the row is selected, your cell won't show that unless you either return false in that case or draw your own special bg color for that case.
Also, what you are talking about is the background color, not foreground color, it seems to me. You could perhaps update your question title accordingly.

Related

Google Sheets: Conditionally Shade an Individual Set of Repeating Groups of Cells Based on Value

I have a Google Sheet I use to document my quotes for customers. I've copied and pasted it down the sheet several times. Within each quote is a list validated field where the options are "Won" or "Lost." When I select "Won," I want to turn that background color of JUST that quote green. However, I also only want to shade green the fields that I am not entering text into or accomplishing the calculations on (the data fields); I want to shade the background of the "form" and the "labels." (See screenshot where I did this by hand.)
I've attempted do this myself, but I'm having two issues.
I have to create separate conditional format rules for each quote/group of cells. This is tedious. I'd like, if possible, to create one rule that is able to adapt the conditional test of ="Won" to each group/quote separately.
I have to individually select each and every cell to be highlighted because if I just do A1:G33, all of the cells change color, even the ones in black and gray. I know this is expected, but is there a way around it?
I'm confused why applying the rule to the first group and copying and pasting it doesn't work. All of the quotes change green if the first quote shows "Won." This functionality works correctly with the built-in conditions that test the value of the cell itself. However, custom formulas to test the value of another cell don't "repeat" and operate individually it seems.
EDIT: Link to spreadsheet: https://docs.google.com/spreadsheets/d/1OH16NXLiRzY3-EdZaxmUl5Vp6cQNPzY-_-LV7VIDl8U/edit#gid=983650786
in your case perhaps best would be to color the first table exactly as you wish and then select it, copy it, select the first cell of the next table and:
Use Apps Script
This way you can have a lot of flexibility and its easier to manage.
You seem to already have an onEdit trigger, so you could integrate some more logic into that to test for statues. I can't implement everything you want but this should get you well on the way.
function onEdit(e){
// Getting the information on the edited cell
let editedCell = e.range;
let sheet = editedCell.getSheet()
// If the edited cell is in column 6, in "Quotes" and is value "Won"
if (editedCell.getColumn() == 6 && sheet.getName() == "Quotes" && editedCell.getValue() == "Won") {
// Get the row to know where to change the format
let statusRow = editedCell.getRow();
// Get the range of the whole form
let formRange = sheet.getRange(statusRow, 3, 17, 6)
// Set the background color
formRange.setBackground("green")
}
}
Hopefully you can see how you might extend this to include other ranges. You would need to base everything of the position of the edited cell. I have identified it by its column, the sheet that contains it, and its value. You can extend this to include checks on the value next to it for example. Then to change the color of each black box, you would need to define a range for each, relative to the edited cell.
References
setBackgroundColor
getRange

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.

Odd drawing issue (black cells) in TStringGrid

I encountered something really really really odd with TStringGrid (Delphi XE). I have seen that sometimes when I click the first line in my grid, it turns black (or shows scrambled canvas 'stolen' from other controls on form).
It happens ONLY in certain configurations, when the grid receives focus. Once you click another area in the grid everything looks ok until the focus is moved to another TStringGrid.
How to reproduce:
put TWO string grids on a form
set them as shown below (Update: I realized that goRowSelect and goEditing must be 'true')
click the first cell in one grid -> nothing happens
click the first cell in the second grid -> the first cell gets black (see screenshot)
The problem appears also in other circumstances (not necessary to have 2 grids on a form), but I managed to reproduce it only when I have 2 grids.
object grid1: TStringGrid <------- same for Grid2
Left = 2
Top = 8
Width = 422
Height = 381
BevelEdges = [beLeft, beTop]
DefaultColWidth = 80
DefaultRowHeight = 15
DoubleBuffered = True
FixedCols = 0
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goDrawFocusSelected, goColSizing, goEditing, goRowSelect, goFixedHotTrack]
ParentDoubleBuffered = False
TabOrder = 1
end
Any idea on how to fix this?
I can reproduce your problem with XE3 as well. After I have reset ParentDoubleBuffered to True, the problem is gone.
Generally speaking, when I see odd black rectangle in a control, I will first check the ParentDoubleBuffered settings. I saw you have enabled double buffering for the two grids. Do you have any special reason to do that? If you intend to avoid flickering during resizing or cell update, there are some techniques helpful.

Blackberry return to the previus position in ListField

In my application I'm using a list.
This list can be updated by a refresh list button which is retriving more data from serever.
When user pressing the refresh button, my application seves the last selected item into a memeber field -
lastPosition = listField.getSelectedIndex();
After updating the list, my application uses -
listField.setSelectedIndex(lastPosition);
for setting the last position.
But nothing happen.
How can i "move" the list's cursor to point on the lastPosition?
Thanks,
Eyal.
listField.setKeyword("");
listField.v = userVector;
listField.updateList();
listField.getResultList();
listField.setSelectedIndex(lastPosition);
listField.setFocus();
Manager manager = listField.getManager();
int rowHeight = listField.getRowHeight();
manager.setVerticalScroll(lastPosition * rowHeight);
listField.invalidate();
It's hard to guess what is wrong without seeing the code, however the first thing that comes to mind is probably you don't set focus on the list right after you restore the position? If you don't, then try it:
listField.setSelectedIndex(lastPosition);
listField.setFocus();

Creating a ComboBox with one or more separator items?

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.

Resources