potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Windows フリーウェア強制バンドルのMcAfee Security Scan Plusをインストールしたら「C:\AVScanner.ini」が作成された。

McAfee Security Scan Plusが強制インストールされるフリーウェアをインストールしたところ、Cドライブ直下に「AVScanner.ini」というファイルが作成されました。NTFSではファイル所有者が管理者になっており、重要なファイルかのように削除に管理者権限が必要でした。中身は以下なのでバンドルしたソフトウェアかインストールされたPCを区別するための番号だと思います。

McAfee Security Scan Plusの「AVScanner.ini」作成または参照を示す公式情報は見つかりませんでしたが、バンドルのインストール時に作成されたので無関係ではないと思います。

[product]
product_affid=<整数>

余談です。「AVScanner.ini」をGoogle検索した結果が興味深かったです。ネット掲示板での解決につながらない質問と回答は良いとして、それを元にしたらしきAI生成ブログがひどい内容でした。興味のある方はGoogle検索のアルゴリズムが変わる前に検索すると面白いかもしれません。通常のブログもヒットするのでAI偏見注意です。

こうやって、リンクのなくなったネットはAI量産情報に塗り替えられるのかなと思います。

C#&Win API MDI子フォームのStatusStripにサイズグリップを強制表示する。

以前の投稿でMDI子フォームはMDI親フォームの最大化時にサイズグリップが非表示になるWinFormsの仕様を紹介しました。この記事ではウィンドウメッセージとToolStripRendererを利用したサイズグリップの強制表示を紹介します。動作確認環境は.NET 8.0ですが、他のバージョンでも変わらないと思います。

使用する仕組みは次の3つです。

暗黙的なglobal usingを使った具体的なコードは以下の通りです。とりあえず機能することを目的としています。プロジェクトで使用するには次のコードを適用なCSファイルに貼り付け、目的とするフォームの「*.Designer.cs」でStatusStripStatusStripWithSizeGripに置き換えてください。

namespace Utility.Windows.Forms; // TODO:適当な名前空間に変更

internal sealed class StatusStripWithSizeGrip : StatusStrip
{
    protected override void OnCreateControl()
    {
        base.OnCreateControl();

        // SizeGripBoundsを使いたいのでサイズグリップを強制表示します。
        // この後の変更は対応しません。
        SizingGrip = true;
    }

    protected override void WndProc(ref Message m)
    {
        const int WM_NCHITTEST = 0x0084;
        const int HTBOTTOMRIGHT = 17;

        if (m.Msg == WM_NCHITTEST)
        {
            var pt = PointToClient(Cursor.Position);
            if (SizeGripBounds.Contains(pt))
            {
                m.Result = HTBOTTOMRIGHT;
                return;
            }
        }

        base.WndProc(ref m);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        // 直接の親がフォームかつ最小・最大化されていなければサイズグリップを描画
        if (Parent is not Form { WindowState: FormWindowState.Normal })
            return;

        // サイズグリップを描画する。描画済みでも上書き。
        Renderer.DrawStatusStripSizingGrip(new(e.Graphics, this));
    }
}

C#&WinForms MDI子フォームのStatusStripでサイズグリップが消える。

追伸:解決方法を別の記事に記載しました。

WinFormsでMDI子フォームにStatusStripを置くとMDI親フォームの最大化時にサイズグリップが消えます。GitHub上のソースコードを調べると設計上の仕様です。具体的には次の通りです。

  • StatusStripはサイズグリップの描画判定時、Win APIGetAncestor関数でルートウィンドウの最大化状態を参照している。
  • MDI子フォームはMDI親フォーム内に存在するため、上記のルートウィンドウはMDI親フォームになる。なのでMDI親フォームが最大化されていればサイズグリップは非表示になる。
  • この仕様はおそらくStatusStripをフォーム以外にも配置できるようにした結果&設計思想。実際には親フォームがMDI子フォームかは順に辿って判定できるが、おそらく判定のコストから実施していない。あとMDIウィンドウは旧式扱いらしいので。

解決策は以下のどれかだと思います。どれも試してはいません。

  • StatusStripのサイズグリップを諦める。
  • StatusStripを使わず、Formのサイズグリップを使う。
  • StatusStripを使うが、Dock指定やコンテナ使用でサイズグリップ分の隙間を空けてフォーム自体のサイズグリップを表示する。
  • StatusStripを使わず、TextBoxやListViewの上下・左右スクロールバーを気合いで表示させてサイズグリップも描画させる。
  • StatusStrip派生クラスを作成してサイズグリップ描画や使用処理を上書きする。

C# WinFormsのクリップボード監視コンポーネント

WinFormsのFormへのクリップボード監視機能の追加、コンポーネントNativeWindowで思ったより簡単に実装できました。NativeWindowの理解が浅いので問題が残っているかもしれませんが、とりあえず動きはします。動作確認はC# (.NET 8.0)ですが、名前空間などの書き方を変えれば以前の.NETでも使えると思います。

プロジェクトにClipboardWatcher.cs等の名前で次のコードを追加して一度ビルドします。後はツールボックスからFormに追加できます。

#pragma warning disable SYSLIB1054

using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Utility.Windows.Forms;

/// <summary>
/// クリップボードの変更を監視します。
/// </summary>
[SupportedOSPlatform("windows")]
[DefaultEvent(nameof(ClipboardUpdated))]
internal sealed class ClipboardWatcher : Component
{
    /// <summary>
    /// クリップボードが変更されました。
    /// </summary>
    public event EventHandler? ClipboardUpdated;

    private sealed class ClipboardWatcherNativeWindow : NativeWindow
    {
        private readonly ClipboardWatcher _owner;
        public ClipboardWatcherNativeWindow(ClipboardWatcher owner)
        {
            _owner = owner;
            CreateHandle(new CreateParams());
        }

        protected override void WndProc(ref Message m)
        {
            const int WM_CLIPBOARDUPDATE = 0x031D;

            switch (m.Msg)
            {
            case WM_CLIPBOARDUPDATE:
                _owner.OnClipboardUpdated(_owner, new());
                break;
            }

            base.WndProc(ref m);
        }

        protected override void OnHandleChange()
        {
            base.OnHandleChange();
            NativeMethods.AddClipboardFormatListener(Handle);
        }

        public override void ReleaseHandle()
        {
            NativeMethods.RemoveClipboardFormatListener(Handle);
            base.ReleaseHandle();
        }

        private static class NativeMethods
        {
            [DllImport("user32.dll")]
            [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool AddClipboardFormatListener(nint hwnd);
            [DllImport("user32.dll")]
            [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool RemoveClipboardFormatListener(nint hwnd);
        }
    }

    private ClipboardWatcherNativeWindow? _native;

    public ClipboardWatcher()
    {
        if (!DesignMode)
            _native = new(this);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _native?.ReleaseHandle();
            _native = null;
        }

        base.Dispose(disposing);
    }

    /// <summary>
    /// <code>ClipboardUpdated</code>イベントを呼び出すかを取得または設定します。
    /// </summary>
    [DefaultValue(true)]
    [Category("Behavior")]
    public bool Enabled { get; set; } = true;

    private void OnClipboardUpdated(object? sender, EventArgs e)
    {
        if (Enabled)
            ClipboardUpdated?.Invoke(sender, e);
    }
}

作成中に学んだこと。

  • 低レベルのWindowsネイティブウィンドウ操作はNativeWindowクラスの派生クラスを作成する。
  • NativeWindowは自動でウィンドウを作らないので、コンストラクタなどでCreateHandleを呼び出す。
  • CreateHandleの引数でクラス名を指定するとサブクラス化ウィンドウが作成できる。System.Windows.Forms.Button等はこの機能でサブクラス化している。

参考

C++20 std::views::splitはsubrange型を返す。

std::views::splitの結果をそのままto<std::vector>に渡してコンパイルエラーに悩まされたのでメモとして。

結論としてstd::views::splitの戻り値をstd::ranges::toSTLコンテナに変換するとき、手前で各要素をstd::wstringstd::wstring_viewに変換する必要があります。std::views::split戻り値の各要素はstd::ranges::subrange型で文字列型への直接変換が実装されていないからです。

下記にサンプルコードを示します。丸ごと補助関数にまとめたり、std::wstringstd::wstring_viewへの変換だけ補助関数にしても良いかもしれません。

#include <ranges>
#include <string>
#include <vector>
#include <iostream>

using namespace std::string_view_literals;

int wmain()
{
    auto s1{ L"ABC\0DEF\0GHI\0\0"sv };

    // std::vector<std::wstring>の作成
    auto sv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring(std::ranges::begin(sr), std::ranges::end(sr)); })
        | std::ranges::to<std::vector>() };

    // std::vector<std::wstring_view>の作成
    auto svv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring_view(sr); })
        | std::ranges::to<std::vector>() };

    return -1;
}

std::views::splitの動作は次のコードで確認できます。MSVC 2022であればstd::ranges::split_viewclass _Iterator::value_typesubrangeが明示されているのでSTLソースコードからも確認できます。

#include <ranges>
#include <string>
#include <vector>
#include <iostream>

using namespace std::string_view_literals;

int wmain()
{
    auto s1{ L"ABC\0DEF\0GHI\0\0"sv };

    for (const auto& i : std::views::split(s1, L'\0'))
    {
        // iはstd::ranges::subrange型です。
        // const std::ranges::subrange<std::_String_view_iterator<std::char_traits<wchar_t>>,std::_String_view_iterator<std::char_traits<wchar_t>>,1> &

        // デバッグウィンドウに表示するための操作
        auto j = i;
    }

    for (const auto& i : std::views::split(s1, L'\0'))
    {
        // std::ranges::subrangeはレンジなのでstd::wstring_viewに変換できます。
        std::wstring_view sv(i);
        // std::wstringはレンジからの作成に未対応なのでエラーになります。
        //std::wstring sv(i);
    }

    // 以下をコメントアウトするとコンパイルエラーになります。
    // std::views::splitの戻り値型std::ranges::subrangeはstd::vectorの要素にできません。
    // 下記のようにtransformでstd::wstring_viewやstd::wstringへ変換すれば解決します。
    //for (const auto& i : std::views::split(s1, L'\0') | std::ranges::to<std::vector>())
    //{
    // std::wstring_view sv(i);
    //}

    // std::wstringはレンジからの作成に未対応です。イテレータペアから作成します。
    auto sv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring(std::ranges::begin(sr), std::ranges::end(sr)); })
        | std::ranges::to<std::vector>() };

    // std::wstring_viewはレンジからの作成に対応しています。記述量が減ります。
    // ただし、元の文字列がリテラルでない場合はメモリが解放されると無効になります。
    auto svv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring_view(sr); })
        | std::ranges::to<std::vector>() };

    return -1;
}

参考