サインイン 2

追記(20230827 OneDriveアプリからオンライン表示へ切り替え)

無駄にプログラムなんか書く必要はありませんでした!

以下、『いかに苦労してOneDriveにサインインするか』という、上記サインイン 4に辿り着くまでの、長いながいまわり道の記録です。なので、お読みいただく価値がないことを最初に申し添えます。m(__)m

この記事は、アプリとして実行(タスクトレイに常駐)するOneDriveではなく、Web上のOneDriveへ直接データをアップロードし、別のPCでそのデータをダウンロードする、言わばデータ交換用USBメモリのようにOneDriveを使用する方法の一例です。PC内のOneDriveフォルダにあるデータと、クラウド上のOneDriveにあるデータの同期などは、まったく考慮しておりませんので、その点にはどうかご注意願います。

また、サインイン画面でのID(Microsoftアカウントに登録したメールアドレス)入力後のOneDriveの応答について、いろいろ調べたのですが、起こり得る個々の問題一つ一つについては、僕のプログラミングスキルでは到底対応できないと感じました。そこで、ID入力後にパスワード入力が必要になった場合の処理について、後日、自分なりのスーパー・ローレベル対応方法として「サインイン3」というタイトルで書きたいと思います。

コンピューターと同期する OneDrive フォルダーを選択する

クラウド上のOneDriveとPCのデータの同期については上のリンク先記事をご参照ください。

OneDriveのサインイン画面に、IDとして利用するメールアドレスを自動入力するプログラムを前回、作成した。目的通りのプログラムが出来たことは出来たが、WebView4Delphiコンポーネント(MITライセンス)のdemoフォルダにあったSampleをそのまま使わせてもらったため、「ID(メールアドレス)を自動入力する」以外にも様々な機能が実装(Sampleなんだから当然と言えば当然)されており、ただプログラムを起動するだけで、exeを置いたフォルダ内に容量がおよそ200MBくらいある「CustomCache」という名前のフォルダが出来てしまう。無視すればイイと言ってしまえば・・・それまでかもしれないけど・・・。

WebView4Delphi

https://github.com/salvadordf/WebView4Delphi

とりあえず、自分的には使わない機能をカットしようと思い、プログラムソースを読んでみたんだけれど・・・、コードどうしの関連がよくわからない。ヘタにいじって不具合とバグの山を築くより、「IDの自動入力」という初心に帰って、プログラムをイチから作り直した方がいい気がしてきた。

で、作ったのがコレ。

OneDriveのサインイン画面にID(メールアドレス)を自動入力する機能だけを搭載

【作成の手順】

1.TEdgeBrowserを使う
2.GUIの設計とプログラムコード
(1)GUIの設計
(2)VCLコントロールの表示/非表示を切り替え
(3)入力値の保存/読み込みと暗号化
(4)カーソル位置の座標を取得
(5)プログラムコードから指定位置をクリック
(6)ダウンロードフォルダを開く
(7)リソースにDLLを埋め込む
(8)操作方法の案内
3.まとめ
4.お願いとお断り

1.TEdgeBrowserを使う

Delphiで、Web コンテンツやローカルに置いたhtmlファイルの読み込みと表示を行うためのビジュアル コンポーネントには、TEdgeBrowser や TWebBrowser があるけれど、表示したいWebページがJavaScript のダイアログ ボックス、パネル、その他要素を使用しているとTWebBrowser では Web ページを正しく表示できないことがあるようだ。

正直に言うと、Edge には印刷その他の不具合でかつて悩まされた記憶があり、個人的にあまり良いイメージを持っていなかったので、新しい TEdgeBrowser コンポーネントではなく、古い TWebBrowser コンポーネントの方を使いたかった。だから、最初は、次のリンク先にあるような情報を参考にして、TWebBrowser コンポーネントで OneDrive のサインイン画面を表示するプログラムを書いてみたのだが・・・

Delphi / C++Builder Starter Edition の VCL で WebBrowser コンポーネントを使う

https://qiita.com/ht_deko/items/c69902d644ea03f61deb

上のリンク先記事のおしまいの部分でも述べられている通り、TWebBrowser コンポーネントを使って OneDrive のサインイン画面を表示するコードを書くと、結構盛大にスクリプトエラー発生のメッセージが表示される。

なんとかならないか・・・と思い、Google先生にお伺いをたてると、FMX版の TWebBrowser の記事ではあるが、本家本元embarcaderoさん提供のスクリプトエラー発生回避策を発見。

FMX.WebBrowser.TWebBrowser

https://docwiki.embarcadero.com/Libraries/Sydney/ja/FMX.WebBrowser.TWebBrowser

それによれば「この問題を回避するには、アプリケーションは、Internet Explorer の FEATURE_BROWSER_EMULATION 機能を使用して、Web ページを IE11 エッジ モードで表示しなければなりません。」と説明があり、具体的な回避策として「FormCreate イベント ハンドラで、TForm1.SetPermissions メソッドを呼び出す」方法がソースコード付きで掲載されていた。

早速、スクリプトエラー回避策なるそのコードをコピペして実行してみたが、ナニがよくないのか、スクリプトエラーは発生状況に変化は見られなかった。

上のリンク先ページでは、スクリプトエラー回避策コードの下に「メモ: レジストリに対するこれらの変更は、アプリケーションが開始する前に適宜行わなければなりません。最初にアプリケーションを開始した際には、それを一度閉じ、再度開始します。」という説明があるので、プログラム起動時にレジストリに対する変更を行って、いったんプログラムを終了し、再度実行すればOKなのか? とも思ったが、原因の究明に時間を割くより、新しいTEdgeBrowserコンポーネントでOneDriveのサインイン画面を表示する方法を試した方が賢い気がして、ここで方針を変更。素直にTEdgeBrowserコンポーネントを使うことにする。その際、参考にさせていただいた記事がこちら

TEdgeBrowserでWebView2を使う ~Delphiソースコード集

https://mam-mam.net/delphi/tedgebrowser.html

OSがWindows11であれば、動作に必要なMicrosoft WebView2 ランタイムは、既に入っているので、インストール不要とのこと。作成するプログラムを動かす予定のPCのOSはすべてWindows11なので、その点は心配ないが、いちおうReadme.txtファイルを用意して、OSがWindows10の場合にはMicrosoft WebView2 ランタイムのインストールが必要であることを案内した方がよさそうだ。

2.GUIの設計とプログラムコード

(1)GUIの設計

Delphiを起動し、「ファイル」→「新規作成」→「Windows VCLアプリケーション」と辿って、表示されたFormにPanelを3つ図のように配置する。

Panel1が階層構造的にはいちばん下にあり、AlignプロパティはalTopを指定。その上にPanel2及び 3 を乗せて、Panel2のAlignプロパティはalLeft、Panel3のAlignプロパティはalClientをそれぞれ指定する。このようにプロパティを設定しておけば、Formの大きさが変化しても、Panel2の大きさ(幅と高さ)は変わらず、Panel3の高さはそのままで幅がFormの大きさ(幅)に合わせて自動的にサイズが変化し、各VCLコントロールの位置はFormの左上を原点とした設計時の位置に固定されて表示される。

VCLコントロールの階層構造

この後、Panel2の上には「ダウンロードフォルダを開く」ボタン、Panel3の上には暗号化してiniファイルに保存する予定の「ID」入力用のEditその他のVCLコントロールを次のように配置する。

必要と思われる最小限度のVCLコントロールを使い勝手を考えながら配置する

(2)VCLコントロールの表示/非表示を切り替え

ID(メールアドレス)や、自動的にクリックする座標値はいったん設定・保存してしまえば、通常の使用の際には必要ないので、普段は非表示に設定。つまり、□設定チェックボックス以外のVisibleプロパティはすべてFalseを指定する。で、設定を☑したときだけ、表示されるように設定。

procedure TForm1.chkSettingClick(Sender: TObject);
begin
  if chkSetting.Checked then
  begin
    LabelID.Visible:=True;
    btnCopy.Visible:=True;
    btnCopy.Enabled:=True;
    Edit1.Visible:=True;
    LabelX.Visible:=True;
    EditX.Visible:=True;
    LabelY.Visible:=True;
    EditY.Visible:=True;
    btnSave.Visible:=True;
    chkZahyo.Visible:=True;
    LabelXY.Visible:=True;
    LabelWaitTime.Visible:=True;
    cmbWaitTime.Visible:=True;
  end else begin
    LabelID.Visible:=False;
    btnCopy.Visible:=False;
    Edit1.Visible:=False;
    LabelX.Visible:=False;
    EditX.Visible:=False;
    LabelY.Visible:=False;
    EditY.Visible:=False;
    btnSave.Visible:=False;
    chkZahyo.Visible:=False;
    LabelXY.Visible:=False;
    LabelWaitTime.Visible:=False;
    cmbWaitTime.Visible:=False;
  end;
end;

(3)入力値の保存/読み込みと暗号化

各VCLコントロールに入力された値は、必要な個所は暗号化してiniファイルに保存する。

uses
  System.IniFiles;

procedure TForm1.btnSaveClick(Sender: TObject);
var
  strID:string;
  Ini:TIniFile;
begin

  //入力の有無をCheck
  if Edit1.Text='' then
  begin
    MessageDlg('IDとして利用するメールアドレスを入力してください', mtInformation, [mbOk] , 0);
    Edit1.SetFocus;
    Exit;
  end;

  if (EditX.Text='') or (EditY.Text='') then
  begin
    if EditX.Text='' then
    begin
      MessageDlg('自動クリックするX座標を入力してください', mtInformation, [mbOk] , 0);
      EditX.SetFocus;
    end;
    if EditY.Text='' then
    begin
      MessageDlg('自動クリックするY座標を入力してください', mtInformation, [mbOk] , 0);
      EditY.SetFocus;
    end;
    Exit;
  end;

  if cmbWaitTime.Text='' then
  begin
    MessageDlg('カーソル移動の待機時間をミリ秒単位で入力してください', mtInformation, [mbOk] , 0);
    cmbWaitTime.SetFocus;
    Exit;
  end;

  //暗号化
  strID:=EDText(Edit1.Text, IntToStr(HashOf('XXXXXXXX')), True);

  //iniファイルに保存
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    //保存
    Ini.WriteString('Section', 'ID', strID);
    Ini.WriteString('Section', 'IchiX', EditX.Text);
    Ini.WriteString('Section', 'IchiY', EditY.Text);
    Ini.WriteString('Section', 'WaitTime', cmbWaitTime.Text);
    //Userに通知
    MessageDlg('現在の設定を保存しました!', mtInformation, [mbOk] , 0);

    if not btnCopy.Enabled then btnCopy.Enabled:=True;

  finally
    Ini.Free;
  end;

end;

コードの中で使用しているEDText関数はテキスト暗号化の関数。

  private
    { Private 宣言 }
    //HashNameMBCS(Create hashed values from a Unicode string)
    //MBCS:Multibyte Character Set=マルチバイト文字セット
    function HashOf(const key: string): cardinal;

    //テキスト暗号化/復号化
    Function EDText(KeyStr,PassW:string; EncOrDec:Boolean):string;
    //KeyStr:平文 or 暗号化文のいずれかを指定
    //PassW:パスワード
    //EncOrDec:True -> Encode / False -> Decode

  public
    { Public 宣言 }
  end;

function TForm1.HashOf(const key: string): cardinal;
var
  I: integer;
begin
  Result := 0;
  for I := 1 to length(key) do
  begin
    Result := (Result shl 5) or (Result shr 27);
    Result := Result xor Cardinal(key[I]);
  end;
end;

function TForm1.EDText(KeyStr, PassW: string; EncOrDec: Boolean): string;
var
  {暗号化用変数}
  Source, Dest, Password:TStringBuilder;
  lpSource, lpPass:Integer;
  PassValue, SourceValue, EDValue:Word;
  {共用変数}
  //乱数の種
  Seed1,Seed2,Seed3:integer;
  //実数の一様乱数
  RandNum:Double;
  //秘密鍵Seed
  Seed:string;
  {復号化用変数}
  DecSource:string;
begin
  //1.シード値を準備
  // (1)Passwordを整数へ変換→シード値1へ代入
  Password := TStringBuilder.Create;
  //Seed1を初期化
  //Seed1:=0;
  try
    Password.Append(PassW);
    PassValue := 0;
    for lpPass := 0 to Password.Length - 1 do
    begin
      //パスワード→整数
      PassValue := PassValue + Word(Password.Chars[lpPass]);
    end;
    Seed1:=PassValue;
  finally
    Password.Free;
  end;

  // (2)パスワード文字列の長さを取得→シード値2へ代入
  Seed2:= ElementToCharLen(PassW,Length(PassW));

  // (3)シード値1とシード値2の排他的論理和を計算して、シード値3へ代入
  Seed3 := Seed1 xor Seed2;

  //2.実数の一様乱数を計算
  //---------------------------------------------------------------------------
  // 0より大きく1より小さい実数の一様乱数を発生する関数
  // B.A.Wichmann and I.D.Hill, Applied Statistics, 31, 1982, p.188 に基づく
  // Seed1-3に入れる初期値(整数)は16bit長(maxint=32767)で十分
  // Seed1-3には1から30000までの任意の整数値を準備する(0ではいけない)
  //---------------------------------------------------------------------------

  //Seed1:=171*Seed1 mod 30269 と同値
  Seed1:=(Seed1 mod 177)*171-(Seed1 div 177)* 2;
  if Seed1<0 then Seed1:=Seed1+30269;
  //Seed2:=172*Seed1 mod 30307 と同値
  Seed2:=(Seed2 mod 176)*172-(Seed2 div 176)* 35;
  if Seed2<0 then Seed2:=Seed2+30307;
  //Seed1:=170*Seed1 mod 30323 と同値
  Seed3:=(Seed3 mod 178)*170-(Seed3 div 178)* 63;
  if Seed3<0 then Seed3:=Seed3+30323;
  //See1-3それぞれの乱数を0<RandNum<1となるように
  //計算結果が0より大きく、1未満の実数に直し、和の小数部分をとる
  RandNum:=(Seed1/30269.0) + (Seed2/30307.0) + (Seed3/30323.0);
  while RandNum>=1 do RandNum:=RandNum-1;

  //3.秘密鍵を生成

  //整数の一様乱数の上限値を決めて、整数の一様乱数を生成し、
  //これに上で計算した実数の一様乱数を加えて秘密鍵を生成する
  //Seedが秘密鍵(文字列として利用)となる
  Seed:= FloatToStr(RandNum + trunc((Seed1+Seed2+Seed3)*RandNum));

  //4.暗号化 / 復号化
  if (EncOrDec) then
  begin
    //暗号化(Encode)
    Source := TStringBuilder.Create;
    Dest := TStringBuilder.Create;
    Password := TStringBuilder.Create;
    try
      Source.Append(KeyStr);
      //秘密鍵をセット
      Password.Append(Seed);
      lpPass := 0;
      //テキストのエンコード
      for lpSource := 0 to Source.Length - 1 do
      begin
        //パスワード→整数
        if Password.Length = 0 then
          PassValue := 0
        else begin
          PassValue := Word(Password.Chars[lpPass]);
          Inc(lpPass);
          if lpPass >= Password.Length then lpPass := 0;
        end;
        //テキスト→整数
        SourceValue := Word(Source.Chars[lpSource]);
        //XOR演算
        EDValue := PassValue xor SourceValue;
        //16進数文字列に変換
        Dest.Append(IntToHex(EDValue, 4));
        //処理結果を返り値にセット
        Result:=Dest.ToString;
      end;
    finally
      Password.Free;
      Dest.Free;
      Source.Free;
    end;
  end else begin
    //復号化(Decode)
    DecSource:=keyStr;
    Dest := TStringBuilder.Create;
    Password := TStringBuilder.Create;
    try
      //暗号化テキストのデコード
      Dest.Clear;
      Password.Clear;
      //秘密鍵をセット
      Password.Append(Seed);
      lpPass := 0;
      for lpSource := 1 to Length(DecSource) div 4 do
      begin
        SourceValue := StrToInt('$' + Copy(DecSource, (lpSource - 1) * 4 + 1, 4));
        if Password.Length = 0 then
          PassValue := 0
        else
        begin
          PassValue := Word(Password.Chars[lpPass]);
          Inc(lpPass);
          if lpPass >= Password.Length then lpPass := 0;
        end;
        EDValue := SourceValue xor PassValue;
        Dest.Append(Char(EDValue));
      end;
      //処理結果を返り値にセット
      Result:=Dest.ToString;
    finally
      Password.Free;
      Dest.Free;
    end;
  end;
end;

サインイン時にIDとして入力するメールアドレスは暗号化されてiniファイルに保存され、FormCreate時にこれを復号して、Editコントロールに表示する。

procedure TForm1.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  strID, strX, strY, strWaitTime: String;
  i:integer;
begin

  //Formを最大化して表示
  Form1.WindowState:=wsMaximized;

  //待ち時間の選択肢(100~3000ミリ秒を100ミリ秒単位で用意)
  for i := 1 to 30 do
  begin
    cmbWaitTime.Items.Add(IntToStr(i*100));
  end;

  //iniファイルの存在を確認
  if FileExists(ChangeFileExt(Application.ExeName, '.ini')) then
  begin
    //iniファイルからデータを読込み
    Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
    try
      strID:=Ini.ReadString('Section', 'ID', '');
      strX:=Ini.ReadString('Section', 'IchiX', '580');
      strY:=Ini.ReadString('Section', 'IchiY', '420');
      strWaitTime:=Ini.ReadString('Section', 'WaitTime', '500');
    finally
      Ini.Free;
    end;
    //復号して表示
    Edit1.Text:=EDText(strID, IntToStr(HashOf('XXXXXXXX')), False);
    EditX.Text:=strX;
    EditY.Text:=strY;
    cmbWaitTime.Text:=strWaitTime;
  end;

  //Navigate
  EdgeBrowser1.Navigate('https://onedrive.live.com/about/ja-jp/signin/');

end;

(4)カーソル位置の座標を取得

マウスのカーソルが現在置かれている位置のスクリーン座標を取得してLabelに表示。

procedure TForm1.chkZahyoClick(Sender: TObject);
begin
  if chkZahyo.Checked then
  begin
    //Enabled
    Timer1.Enabled:=True;
  end else begin
    //Enabled
    Timer1.Enabled:=False;
    LabelXY.Caption:='[X座標, Y座標]';
  end;
end;

Timer1のOnTimerプロパティをダブルクリックして作成されたTimer1Timer手続きに次のコードを記述。これでほぼリアルタイムにカーソルの位置座標を取得して表示できる。

procedure TForm1.Timer1Timer(Sender: TObject);
var
  lh_Handle:  HWND;
  lpt_Pos:    TPoint;
  lrc_Rect:   TRect;
  lrg_Region: HRGN;
  li_Ret:     Integer;
begin
  if chkZahyo.Checked then
  begin
    //マウスカーソル位置をスクリーン座標で取得
    GetCursorPos(lpt_Pos);
    //自身のウィンドウリージョンを調べる
    lh_Handle := Self.Handle;

    //ウィンドウリージョン取得のため空のリージョンを作っておく
    lrg_Region := CreateRectRgn(0,0,0,0);
    try
      //ウィンドウリージョン取得
      li_Ret := GetWindowRgn(lh_Handle, lrg_Region);
      if (li_Ret <> ERROR) then begin
        //ウィンドウのRectを取得
        GetWindowRect(lh_Handle, lrc_Rect);
        //スクリーン座標からウィンドウの左上を原点とした座標に変換
        lpt_Pos.X := lpt_Pos.X - lrc_Rect.Left;
        lpt_Pos.Y := lpt_Pos.Y - lrc_Rect.Top;
        //ウィンドウリージョン内にマウスカーソルがあるかテスト
        if (PtInRegion(lrg_Region, lpt_Pos.X, lpt_Pos.Y)) then begin
          LabelXY.Caption:=Format('OK %d (%d-%d)', [li_Ret, lpt_Pos.X, lpt_Pos.Y]);
        end else begin
          LabelXY.Caption:=Format('NG %d (%d-%d)', [li_Ret, lpt_Pos.X, lpt_Pos.Y]);
        end;
      end else begin
        LabelXY.Caption:=Format('[X:%d, Y:%d]', [lpt_Pos.X, lpt_Pos.Y]);
      end;
    finally
      DeleteObject(lrg_Region);
    end;
  end;
end;

(5)プログラムコードから指定位置をクリック

前回作成したプログラムでいちばん、悩んだのがここ。最初はサインイン画面のウィンドウハンドルを取得して文字列を送信しようと思ったんだけれど・・・これがうまくいかない。その詳細は前回の記事を参照してください。

さんざん悩んで、ようやく思いついた方法がプログラムコードで画面上の任意の位置をクリックする方法。Formが完全に描画された段階で、指定位置のクリックと、その位置への文字列の入力を実行している。そのコードを再掲。

  private
    { Private 宣言 }

    //アドレス貼り付け実行の成否
    boolInput:boolean;

    fgWaitBreak : boolean;  //変数は「functionより先に定義」する

    //待ち関数  指定カウントが経過すれば True, 中断されたならば False
    function WaitTime(const t: integer): Boolean;

    //Formの表示終了イベントを取得
    procedure CMShowingChanged(var Msg:TMessage); message CM_SHOWINGCHANGED;


//待機関数
function TForm1.WaitTime(const t: integer): Boolean;
var
  Timeout: TDateTime;
begin
  //待ち関数  指定カウントが経過すれば True, 中断されたならば False
  fgWaitBreak := False;
  Timeout := Now + t/24/3600/1000;
  while (Now < Timeout)and not fgWaitBreak do begin
    Application.ProcessMessages;
    Sleep(1);
  end;
  Result := not fgWaitBreak;
end;


procedure TForm1.CMShowingChanged(var Msg: TMessage);
var
  dwFlags : DWORD;
  X,Y : Integer;
  LKeyByte : Byte;
begin
  inherited; {通常の CMShowingChagenedをまず実行}
  if Visible then
  begin

    Update; {完全に描画}

    if Edit1.Text='' then
    begin
      Edit1.SetFocus;
      Exit;
    end;

    if (EditX.Text='') or (EditY.Text='') then
    begin
      if EditX.Text='' then EditX.SetFocus;
      if EditY.Text='' then EditY.SetFocus;
      Exit;
    end;

    fgWaitBreak:=False;

    //さらに念のためちょっと待機
    WaitTime(StrToInt(cmbWaitTime.Text));

    dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;

    //クリック位置を取得
    X:=Trunc(StrToInt(EditX.Text)/Screen.Width*65537);
    Y:=Trunc(StrToInt(EditY.Text)/Screen.Height*65535);

    //移動
    Mouse_Event(dwFlags,X,Y,0,0);
    Application.ProcessMessages;

    //クリック
    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    Application.ProcessMessages;

    WaitTime(StrToInt(cmbWaitTime.Text));

    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    Application.ProcessMessages;
    Mouse_Event(MOUSEEVENTF_LEFTUP,0,0,0,0);

    //文字列を送信
    boolInput:=False;
    try

      //クリップボードを初期化
      Clipboard.Clear;

      //文字列をクリップボードへ
      Clipboard.AsText:=Edit1.Text;
      
      //[Ctrl] + [V] のキー操作
      LKeyByte := Ord('V');
      keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0);
      keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   0, 0);
      keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   KEYEVENTF_KEYUP, 0);
      keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0);
      //操作に成功
      boolInput:=True;
    except
      //操作に失敗
      boolInput:=False;
    end;

    //貼り付け操作に成功した場合は入力ボタンを操作不可に設定
    if boolInput then btnCopy.Enabled:=False;

  end;

end;

{入力ボタンのClick手続きは、確認メッセージの表示以外は上のコードとほとんど同じ}

また、予期せぬ事故を防止するため、プログラムの終了時にはクリップボードを空に(初期化)する。

procedure TForm1.FormDestroy(Sender: TObject);
begin
  //クリップボードを初期化
  Clipboard.Clear;
end;

(6)ダウンロードフォルダを開く

OneDriveからデータのダウンロードが無事終了すれば、次のようにダウンロードフォルダを開くリンク付きのWindowが表示されるから、特殊なフォルダである「ダウンロードフォルダを開く」ボタンは、別になくてもかまわない気もするけど。

このWindowは移動できない?

もしかしたら任意のタイミングで、それを開きたい時があるかもしれない。エクスプローラーを開けばいいじゃないかという意見は、ここでは聞かなかったことに。

ダウンロードフォルダを開くプログラムコード。

uses
  Vcl.Clipbrd, System.IniFiles, System.UITypes,
  Winapi.ShlObj, Winapi.KnownFolders, Winapi.ShellAPI;

procedure TForm1.btnOpenDLFolderClick(Sender: TObject);
var
  FolderID:TGUID;
  FolderPath:PChar;
  D_FolderPath, ExeFileName:string;
  LhInst:Cardinal;
begin
  FolderID:=StringToGUID('{374DE290-123F-4565-9164-39C4925E467B}');
  if SHGetKnownFolderPath(FolderID,0,0,FolderPath)= S_OK then
  begin
    D_FolderPath := FolderPath;
    //確認
    //ShowMessage(D_FolderPath);
    //ダウンロードフォルダを開く
    ExeFileName:= 'explorer.exe';
    LhInst:=ShellExecute(Handle, 'open', PChar(ExeFileName), PChar(D_FolderPath), nil, SW_SHOW);
    if LhInst <= 32 then
    begin
      MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
    end;
  end;
end;

(7)リソースにDLLを埋め込む

このプログラムの動作には「WebView2Loader.dll」が必須(WebView2Loader.dll は、アプリがデバイス上で WebView2 ランタイム (Microsoft Edge プレビュー チャネル) を見つけるのに役立つコンポーネントであるとのこと)。

WebView2 アプリを 1 つの実行可能ファイルとして配布する

https://learn.microsoft.com/ja-jp/microsoft-edge/webview2/how-to/static

このDLLがないと困るので、添付忘れを防止するため、リソースに埋め込んでおいて、プログラムの実行時にexeのある場所にその有無を確認し、なければリソースから生成するように設定。

メニューの「プロジェクト」→「リソースと画像」で埋め込むDLLを指定

で、FormCreate時に有無を確認、なければexeのある場所に生成。

procedure TForm1.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  strID, strX, strY, strWaitTime: String;
  i:integer;
  dllFileName:string;
begin

  //リソースからDLLを(なければ)生成
  //rijnファイルの位置を指定
  dllFileName:=ExtractFilePath(Application.ExeName)+'WebView2Loader.dll';
  //rijnファイルの存在を確認
  if not FileExists(dllFilename) then
  begin
    //リソースを再生
    with TResourceStream.Create(hInstance, 'Resource_1', RT_RCDATA) do
    begin
      try
        SaveToFile(dllFileName);
      finally
        Free;
      end;
    end;
  end;

  ・・・

end;

(8)操作方法の案内

この他に、画面最下部に設置したStatusBarに次のような案内を表示できるようにした。

操作方法の案内をStatusBarに表示(OKをクリックすると消える)
案内を表示する/しないはユーザーが選択して、その設定状態の保存も可能に

操作方法の案内の表示/非表示の切り替え。

procedure TForm1.chkInfoClick(Sender: TObject);
var
  strInfo:string;
  strWidth:integer;
begin
  if chkInfo.Checked then
  begin
    //表示する文字列
    strInfo:='ID(メールアドレス)が自動入力されないときは、Ctrl+V で入力できます!';
    strWidth:=StatusBar1.Canvas.TextWidth(strInfo);
    btnOK.Visible:=True;
    with btnOK do
    begin
      Parent:=StatusBar1;
      Left:=strWidth-20;
      Top:=1;
    end;
    //StatusBar1の設定(重要:このプロパティがFalseだとStatusBarにテキストが表示されない)
    StatusBar1.SimplePanel:=True;
    //Info
    StatusBar1.SimpleText:=strInfo;
  end else begin
    StatusBar1.SimpleText:='';
    btnOK.Visible:=False;
  end;
end;

案内を「表示する」が選ばれていた場合はFormCreate時に案内表示を出すよう設定。

procedure TForm1.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  strID, strX, strY, strWaitTime: String;
  i:integer;
  dllFileName:string;
  strWidth:Integer;
  strInfo:string;
  boolInfo:boolean;
begin

  if chkInfo.Checked then
  begin
    //表示する文字列
    strInfo:='ID(メールアドレス)が自動入力されないときは、Ctrl+V で入力できます!';
    strWidth:=StatusBar1.Canvas.TextWidth(strInfo);
    with btnOK do
    begin
      Parent:=StatusBar1;
      Left:=strWidth-20;
      Top:=1;
    end;
    //StatusBar1の設定(重要:このプロパティがFalseだとStatusBarにテキストが表示されない)
    StatusBar1.SimplePanel:=True;
    //Info
    StatusBar1.SimpleText:=strInfo;
  end;

  ・・・

  //iniファイルの存在を確認
  if FileExists(ChangeFileExt(Application.ExeName, '.ini')) then
  begin
    //iniファイルからデータを読込み
    Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
    try
      strID:=Ini.ReadString('Section', 'ID', '');
      strX:=Ini.ReadString('Section', 'IchiX', '580');
      strY:=Ini.ReadString('Section', 'IchiY', '420');
      strWaitTime:=Ini.ReadString('Section', 'WaitTime', '500');
      boolInfo:=Ini.ReadBool('Section','Info',True);
    finally
      Ini.Free;
    end;
    //復号して表示
    Edit1.Text:=EDText(strID, IntToStr(HashOf('adminy')), False);
    EditX.Text:=strX;
    EditY.Text:=strY;
    cmbWaitTime.Text:=strWaitTime;
    chkInfo.Checked:=boolInfo;
  end;

  ・・・

end;

案内そのものを表示したくない場合は、ユーザーの自由意思でその設定も可能に。

procedure TForm1.btnSaveClick(Sender: TObject);
var
  strID:string;
  Ini:TIniFile;
begin

  //入力の有無をCheck
  ・・・

  //暗号化
  strID:=EDText(Edit1.Text, IntToStr(HashOf('adminy')), True);

  //iniファイルに保存
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    //保存
    Ini.WriteString('Section', 'ID', strID);
    Ini.WriteString('Section', 'IchiX', EditX.Text);
    Ini.WriteString('Section', 'IchiY', EditY.Text);
    Ini.WriteString('Section', 'WaitTime', cmbWaitTime.Text);
    Ini.WriteBool('Section','Info',chkInfo.Checked);
    //Userに通知
    MessageDlg('現在の設定を保存しました!', mtInformation, [mbOk] , 0);

    if not btnCopy.Enabled then btnCopy.Enabled:=True;

  finally
    Ini.Free;
  end;

end;

3.まとめ

(1)TEdgeBrowserを使えばOneDriveのサインイン画面をエラーなしで表示できる。
(2)サインイン画面へのIDの入力はプログラムコードで実行可能。
(3)IDはクリップボードに送信しておき、Ctrl+Vでも貼り付け可能に設定。

4.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。