SalmiSoft 102 Junior Poster

Chris, a good technique to trace errors like this is to simply use a pencil & paper and manually follow the instructions in your code. Set & change the values of your variables as you work through the code and your errors should become apparent. Later you will no doubt learn to use the debugger instead of pencil & paper.

There are numerous style issues (indenting, variable naming, layout, comments ...) that could be improved in your code, and 2 main errors.
The first error, as pointed out by ddanbe, is that you only need to calculate 1 average, not 10. The code he suggests will get around that problem. Then when you make that change to a single average you will need to change your comparisons and you may end up with something like this:

        for i:=1 to 10 do
            if (average<pin[i]) then
                min_pl:=min_pl+1
            else if (average>pin[i]) then
                max_pl:=max_pl+1;

This shows that your comparisons are the wrong way around, so your max_pl and min_pl values will be swapped. Better to use:

 if (pin[i] < average) then
   min_pl:=min_pl+1
 else if (pin[i] > average) then
   max_pl:=max_pl+1;
SalmiSoft 102 Junior Poster

There is no in-built way to do it so you have to roll your own. There are 2 sides to this problem:
To get the data as booleans rather than dates you could probably use Calculated fields.
To display the data using checkboxes take a look at http://delphi.about.com/od/delphitips2009/qt/dbgrid-checkbox.htm

SalmiSoft 102 Junior Poster

Post the code you tried.

SalmiSoft 102 Junior Poster

If all you want to do is to launch the app then the easiest way is not to find it at all. Just lauch the EXE without specifying its path (for example, Notepad.exe rather than c:\blah\blah\blah\Notepad.exe) and let the OS find it for you. If the EXE is in your path this should work and is worth a try to avoid a possible delay whilst you search for the exe.

If you do have to search for the EXE just remember that an EXE is just a file so you could to use FindFirst and FindNext to locate it. There are many examples out there. A reasonable place to start is http://stackoverflow.com/questions/5991040/how-to-search-different-file-types-using-findfirst

SalmiSoft 102 Junior Poster

I don't see anything obviously wrong with your code so I would look at your data. For example, you check:

if not VarIsNull(WS.Cells[X,3].Value) then

but what about

MillStyle:=WS.Cells[X,2].Value;

Can WS.Cells[X,2].Value ever be null? What effect would it have if it were null?

Also I suggest you step through this in the debugger and examine the variables. What are the values of WS.Cells[X,2].Value and WS.Cells[X,3].Value which produce an erroneous match? What value of CustSKU does the match produce?

SalmiSoft 102 Junior Poster

There is a nice Marquee component at http://www.delphiarea.com/products/delphi-components/marquee/
It can scroll left or write. All you would need to do is handle its onWrap event and swap its direction, like this:

procedure TMainForm.MarqueeWrap(Sender: TObject);
begin
  if Marquee.BiDiMode = bdLeftToRight then
    Marquee.BiDiMode := bdRightToLeft
  else
    Marquee.BiDiMode := bdLeftToRight;
  Marquee.Reset;
end;

The component is really nice because you can scroll HTML text & images so it can look much better than a label.

SalmiSoft 102 Junior Poster

Start a new project.
Add a status bar and set SimplePanel TRUE.
Add a TWebBrowser (and set Align alClient).
Add this for the form's onCreate event handler:

procedure TForm2.FormCreate(Sender: TObject);
begin
  Application.OnMessage := MyMessages;
  wb1.Navigate('https://www.google.com/');  // Or whatever site you want
end;

Add a private procedure to the form:

procedure MyMessages(var Msg: TMsg; var Handled: Boolean);

Implement that procedure like this:

procedure TForm2.MyMessages(var Msg: TMsg; var Handled: Boolean);
var
  X, Y: Integer;
  document,
  E: OleVariant;
begin
  if (Msg.message = WM_LBUTTONDOWN) and IsDialogMessage(wb1.Handle, Msg) then
  begin
    X := LOWORD(Msg.lParam);
    Y := HIWORD(Msg.lParam);
    document := wb1.Document;
    E := document.elementFromPoint(X, Y);
    sbar1.SimpleText := 'You clicked on:' + E.outerHTML;
  end;
  Handled := False;
end;

Run the app and click on a button; the status bar shows something like:

'You clicked on: <input name="btnI" aria-label="I'm Feeling Lucky" type="submit" value="I'm Feeling Lucky" jsaction="sf.lck">'

You could check that text and call whatever routine you like based on the content.
Would this help you do what you want?

Oh, and if you don't want the web page to react to the click in the normal way (so clicking the button only runs your procedure without actually clicking the button) you should set Handled to TRUE.

SalmiSoft 102 Junior Poster

The Loan Arranger.
You could use the Finance icon supplied with Delphi. Not very nice & just a pile of coins, but at least you have it already.

Mya:) commented: thank you +2
SalmiSoft 102 Junior Poster

Can you use the MyRoomQuery.AfterUpdateExecute event? Put the if ... then goto ... code in a handler for that event?

SalmiSoft 102 Junior Poster

Oh that is interesting. It would be nice to see the code for NormalizeString to see what is really going on, but in the absence of that here is my understanding.

The documentation says:

Returns the length of the normalized string in the destination buffer. If cwDstLength is set to 0, the function returns the estimated buffer length required to do the actual conversion.

So the value returned by our first call to NormalizeString only gives us an estimate of the required buffer size and it turns out that the estimate is wrong for your test data. So when we call again the routine fails and the value returned by that call is a (more?) accurate value for the buffer size required. So we have to call yet again using that size of buffer and this should (at last) give us the result we want. So you have to check for errors when you call NormalizeString.

Putting that into code gives:

function NormalizeText(Str: string): string;
var
  nLength: integer;
  c: char;
  i: integer;
  temp: string;
  CatStr:string;
  TheProblem : Cardinal;
begin
  nLength := NormalizeString(NormalizationD, PChar(Str), Length(Str), nil, 0);
  SetLength(temp, nLength);

  nLength := 
    NormalizeString(NormalizationD, PChar(Str), Length(Str), PChar(temp), nLength);

  if nLength <= 0 then
  begin
    TheProblem := GetLastError;
    case TheProblem of

      ERROR_INSUFFICIENT_BUFFER:
        begin
          // Our previous call to NormalizeString gave an ESTIMATED size for the
          // buffer we need., but that estimate was wrong. We now have a new estimate.
          // Give it a try with that estmate and if it still …
SalmiSoft 102 Junior Poster

Try this:

function NormalizeString(NormForm: Integer; lpSrcString: LPCWSTR; cwSrcLength: Integer;
 lpDstString: LPWSTR; cwDstLength: Integer): Integer; stdcall; external 'C:\WINDOWS\system32\normaliz.dll';
function NormalizeText(Str: string): string;
var
  nLength: integer;
  c: char;
  i: integer;
  temp: string;
  CatStr:string;
begin
  nLength := NormalizeString(NormalizationD, PChar(Str), Length(Str), nil, 0); // New line
  SetLength(temp, nLength);  // Modified line

  nLength := NormalizeString(NormalizationD, PChar(Str), Length(Str), PChar(temp), nLength); // Modified line
  SetLength(temp, nLength);  // New line

  CatStr:='';
  for i := 1 to length(temp) do
     begin
     c:=temp[i];
     if (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucNonSpacingMark) and // Modified line
        (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucCombiningMark) then
         CatStr:=CatStr+c;
     end;
  result:=CatStr;
end;
SalmiSoft 102 Junior Poster

Oh, and with your other problem (extra characters) I suspect there could be a problem/confusion with length. You are setting the output string to the same length as the source string, but that is probably incorrect since the API call will split single (accented) characters into multiple characters. I suggest you call NormalizeString twice: once to find out the length to which your output string should be set, and once to do the normalization. The result of the first call should be the final parameter in the second.

SalmiSoft 102 Junior Poster

The declaration is correct, so the problem is elsewhere.
I don't have Delphi on this machine so can't test anything at the moment, but one simple problem is your code:

     if (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucNonSpacingMark) or
        (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucCombiningMark) then

This will ALWAYS be true. I guess you meant to write:

     if (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucNonSpacingMark) and
        (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucCombiningMark) then

I'll take another look at this later when I have access to Delphi.

SalmiSoft 102 Junior Poster

A binary search may be implemented using recursion. Take a look at
http://rosettacode.org/wiki/Binary_search#C.2B.2B

SalmiSoft 102 Junior Poster

What form does your data take?
If you have an input string as a pointer to a null-terminated array of char, you can just walk the string a find the spaces. So you would check the first char then increment the pointer and check the next char. Repeat until you get to the null. Either that or just just use an index into the array ( MyString[i] ) and just increment the index (i) until you get to the null.

SalmiSoft 102 Junior Poster

Whilst tinstaafl's approach has much to recommend it I personally do something similar to ddanbe. My view is that the tools I use most have simple ways of renaming controls and propogating the changes throughout the code, so the impact of changing the name when the type changes is minimal.

Where I differ from ddanbe is that I put the type as a prefix to the name rather than as a suffix. That makes it easier for me to find when I know I want a label (or combo box or whatever) but I am unsure what I called it. Particularly in an alphabetical list of controls it is easier to find lblUserNamePrompt than UserNamePromptlbl, especially if my memory is playing tricks and it is really called lblLogOnNamePrompt. In an alphabetical list all the labels appear together so I have fewer names to scan to find the one I want.

SalmiSoft 102 Junior Poster

TMactacHash.ValidatePrime will only ever set its result to TRUE if number = 2. Change from this:

begin
  if number = 2 then
    begin
      Result := true;
      Exit;
    end;

to this:

begin
  Result := true;
  if number = 2 then
      Exit;
SalmiSoft 102 Junior Poster

OK, so not much to correct.

Sqrt(fValue)

Which compiler are you using? I am surprised that any compiler will accept this because the loop limits should be assignment-compatible with the loop counter. Change to:

trunc(Sqrt(fValue))

On the same line, your loop checks all possible factors but that should not include 1. By definition, a prime number must be greater than 1. So if Value > 1 run your existing code but start the FOR loop from 2 instead of 1. If Vale <= 1, return FALSE. So you end up with:

  if Value > 1 then
  begin
    flag := true;
    fValue := value;
    for i := 2 to trunc(Sqrt(fValue)) do
      begin
        overValue := value mod i;
        if overValue = 0 then
          flag := false;
        WriteLn(IntToStr(value) + ' mod ' + IntToStr(i) + ' = ' + IntToStr(overValue));
        readln;
      end;
    Result := flag;
  end
  else
    Result := false;

With these changes I think your code will do what you want.

Regarding efficiency, I wouldn't worry about it. This code will be quite quick enough for any practical purposes unless you call it many many times, but why would you do that when the results will be the same each time?
If you wanted to make it more efficient without major changes, exit the loop in isPrime as soon as you get a false result. (If your compiler doesn't support the Break command, change to a while loop and check the result is still true as part …

SalmiSoft 102 Junior Poster

The string list has only 1 CommaText so when you write this:

sl.CommaText := NewItem.Caption+'='+IntToStr(NewItem.Tag);

it replaces the existing contents of the string list. I guess you really want to add the new item details to the existing string list contents:

sl.Add( NewItem.Caption+'='+IntToStr(NewItem.Tag) );
SalmiSoft 102 Junior Poster

One problem is that your isPrime method will always return FALSE because you check all factors from 1 upwards, and any value mod 1 = 0.

I am having problems understanding your code. It seems primeset2 to primeset8 are never used, and I don't follow your logic about the sets. Please post your full code -so that it will compile and run - and I'll take another look.

SalmiSoft 102 Junior Poster
sl.StrictDelimiter := TRUE;
sl.CommaText := NewItem.Caption+'='+IntToStr(NewItem.Tag);
SalmiSoft 102 Junior Poster

How have you defined primesets, SET1 to SET8 and MAX_TIMES?

In your IsPrime routine you don't need to loop all the way to Value. Looping to Value/2 will do, because any number greater than Value/2 cannot be a factor of Value.

SalmiSoft 102 Junior Poster

Study this code:

var
  EXEName,
  EXEFolder,
  ParentFolder,
  DataFolder : string;
begin
  Memo1.Clear;
  EXEName := Application.ExeName;
  EXEFolder := ExtractFilePath(ExeName);
  ParentFolder := ExtractFilePath(ExcludeTrailingPathDelimiter(EXEFolder));
  DataFolder := IncludeTrailingPathDelimiter(ParentFolder) + 'Data';

However, although this should answer the question you asked, it doesn't answer the question you should have asked. The point pritaeas made is that your app should not be using that directory structure. Instead it should store application data in the folder returned by:

GetSpecialFolderLocation(CSIDL_APPDATA);

This will not return the folder you are actually using, because your app's data is in the wrong place. It will return the folder where you really should put the data. Having said that, this approach is now out of date and for new code you should use ShGetKnownFolderPath and FOLDERID_RoamingAppData but this whole area is a real mess. The API has changed several times and it isn't an easy task to get it right if you need to run on a variety of Windows versions. Also I think the VCL doesn't import ShGetKnownFolderPath for you so you would need to do that (and define GUIDs for the FOLDERID constants) yourself.

SalmiSoft 102 Junior Poster

Make a search for sample VBA code that calls the API function FindFirstFile. Click Here

SalmiSoft 102 Junior Poster

Rather than regularly rebuilding the list, can you maintain it? Every time a new user appears add him/her to your list, and remove them from your list when they go away?

SalmiSoft 102 Junior Poster

What "bit of trouble" are you having? Are you getting an error message? What is going wrong?

What is WBLoadHTML?

Each element in the string list is an HTML document. You call WBLoadHTML for each one. What is WBLoadHTML supposed to do? Open a new tab for each page? Display each page for a time before you call it again?

If you want to display all the images on the same page then I suspect you should put your loop inside your <TR> tags to load each image then call WBLoadHTML only once. If so, Gallery should hold the image file names rather than the HTML, and you should pass your Page string as the second param in WBLoadHTML

SalmiSoft 102 Junior Poster

Interesting. It works fine here. I nade a simple test app with a web browser, and edit box, and a button. The code I used was as I posted above plus:

procedure TForm2.Button1Click(Sender: TObject);
begin
  if DownloadFile(Edit1.Text, 'C:\Users\MyName\Documents\Test.JPG') then
    WebBrowser1.Navigate('C:\Users\MyName\Documents\Test.JPG');
end;

I put
http://www.team-mplayer.net/portraits/02002/00002.jpg
in the edit box, clicked the button, and the image appeared correctly in the web browser. Can you try that?

SalmiSoft 102 Junior Poster

That is odd. I haven't had any issues with that code. What is the URL for the file you want?

SalmiSoft 102 Junior Poster
function DownloadFile(URL : string; FileName : string) : boolean;
var
  IdHTTP: TIdHTTP;
  FileStream : TFileStream;
begin
  try
    IdHTTP := TIdHTTP.Create(nil);
    try
      FileStream := TFileStream.Create(FileName, fmCreate);
      try
        IdHTTP.Get(URL, FileStream);
      finally
        FileStream.Free;
      end;
      Result := TRUE;
    finally
      IdHTTP.Free;
    end;
  except
    Result := FALSE;
  end;
end;
SalmiSoft 102 Junior Poster

You can use an image list and still allow users to load their own icons into that list at run time. It would be much better to load the custom images into an image list rather than load them into the list view, with all the extra work that involves in splitting the single bitmap into multiple icons. Changing the images in the image list this way should not produce any flicker when the list view gets updated with the new images.