@itagagaki (板垣 史彦)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Visual Studioでプロセスにアタッチするとウィンドウが二重になるのはなぜか?

解決したいこと

WindowsでWPFアプリを開発しています。

このアプリで生成したウィンドウがシステム標準の「音量ミキサー」に加わるとき、音量ミキサーはアプリの(EXEの)アイコンではなく、ウィンドウに設定したアイコンを表示するようです。

できればそれを避けたい(表示するならアプリのアイコンにしてほしい)、というのが、本来解決したいことです。とは言え、音量ミキサー自体を変えることは当然できないわけですが、何か回避できる方法が無いかを探っています。

質問したいこと

そのためいろいろ試行錯誤している中で、次のことに気が付きました。

アプリを単純に実行した場合は上記のとおりなのですが、Visual Studioで「デバッグの開始」で実行するか、実行後に「プロセスにアタッチ」すると、音量ミキサーはアプリのアイコンを表示するようでした。

それはなぜかを調べるため GUIPropView でシステムが開いているウィンドウを観察してみたところ、デバッガーアタッチしているときは、GUIPropVeiw に表示されるウィンドウの数が倍になります。各ウィンドウの親ハンドルを見るに、アプリが作成したすべてのウィンドウに子ウィンドウが付加されているような形に見えます。そしてそれらの子ウィンドウのアイコンは、親ウィンドウのアイコンではなく、アプリのアイコンになっています。おそらくこのとき音量ミキサーがウィンドウのアイコンを取得するとき、子ウィンドウのほうが参照されているのではないかと推測しています。

長くなりましたが、とりあえず質問したいことは、デバッガーアタッチしていると、なぜそのようなことが起こるのか?を知りたいです。

(そして、もし同じ状態を簡単に作れたら、それで上記の問題を回避できるのではないか?と思っています)。

ご存じの方がいらっしゃいましたら教えてください。

ソースコード

下記のシンプルなコードでも確認することができました。
(たまたま以前何かのために書いたテストコードが残っていたのを使ってみただけなので、この内容へのツッコミは不要です)

C#

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Height="450" Width="800">
    <Grid>
        <ListView>
            <ListView.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Menu Item 1" />
                    <MenuItem Header="Menu Item 2" />
                </ContextMenu>
            </ListView.ContextMenu>
            <ListView.Items>
                <ListViewItem>Item 1</ListViewItem>
                <ListViewItem>Item 2</ListViewItem>
                <ListViewItem>Item 3</ListViewItem>
                <ListViewItem>Item 4</ListViewItem>
                <ListViewItem>Item 5</ListViewItem>
            </ListView.Items>
        </ListView>
    </Grid>
</Window>
0 likes

2Answer

面白い挙動だと思い諸々確認したところ、プロジェクト設定からWin32リソースにあるアイコンを設定しておくと、音量ミキサーでそのアイコンが使われるようでした。アプリケーションのアイコンも変更するような要件でなければ、これで十分そうです。

おそらくこのとき音量ミキサーがウィンドウのアイコンを取得するとき、子ウィンドウのほうが参照されているのではないかと推測しています。

上記のことから、音量ミキサーはWin32アイコン→ウィンドウのアイコン、の順で取得されているのだと思います。「音を出しているウィンドウのアイコン」と考えると、納得がいきそうです(詳しくないので推測です。すみません!)

表示されないウィンドウを作り、そのウィンドウで音を鳴らすようなことも考えられますが、実装の複雑さから避けたいですね...

1Like

Comments

  1. @itagagaki

    Questioner

    ありがとうございます。

    Win32リソースはすでにアイコンを設定しており、残念ながら私のほうでは問題を解決することはできませんでした。

    しかし、

    表示されないウィンドウを作り、そのウィンドウで音を鳴らすようなことも考えられますが

    これが大きなヒントとなり、下記の方法で問題を解決することができました!

    アプリを起動してメインウィンドウが開いたとき、ダミーの音(1秒間の無音wav)を鳴らしておく。すると音量ミキサーはそれを察知して、アプリアイコンとともに表示する。その後、音を鳴らす新しいウィンドウを開き、そのウィンドウにアプリとは別のアイコンをセットしても、音量ミキサーはアイコンを変更せずそのままアプリアイコンを表示し続ける。ウィンドウを開いても閉じても変わらず、アプリを終了させるまでそのままでい続けてくれる。

    音量ミキサーは、ウィンドウタイトルの変更は反映するのですが、どうもアイコンは一度取得したら再取得しないような挙動のようですね。

    気持ち的に、多少のモヤモヤは残りますが、UI/UXとしての問題はこれで解消できましたので、これで良しとしたいと思います。

    ありがとうございました!

デバッガはデバッグ対象のプログラム(以下ターゲットプロセスと呼びます)を監視するために、OS(Windows)からターゲットプロセスに送られるイベント(Windows Message)を把握する必要があります。
OSから送られるメッセージの多くはプロセスではなくウィンドウ(hWnd)に対して送られるので、デバッガはそのメッセージを横取りするための偽のウィンドウを作り、そこで受け取ったメッセージをデバッガの情報として記録した後で本来受け取るべきターゲットプロセスのウィンドウに再送信することでターゲットプログラムはデバッガがない時と変わらない動作となります。
つまりターゲットプロセスのウィンドウの数だけ新たなウィンドウが作られるのはVisualStudioデバッガの仕様であり、回避することはできません。

音量ミキサーについてはよくわからないのですが、音量ミキサーに拡張機能的なものを登録するのは「ウィンドウハンドル」ではないでしょうか。
だとすればそこから得られるアイコン情報はウィンドウのアイコンとなるのは当然で「表示したいアイコンをウィンドウアイコンとして設定する」以外の方法はないのではないかと思います。

デバッガにアタッチする場合はアタッチする対象が「プロセス」なので、デバッガが偽のウィンドウを作る際に個々のウィンドウアイコンではなくプロセスアイコンを設定しているのだと思われます。
同じようなことを実現しようとするなら「アイコン表示のためだけに偽のウィンドウを経由する形で登録する」ならば可能でしょうが、そんなことをするくらいなら個々のウィンドウにプロセスアイコンを設定する方がよっぽど楽だと思われるので、そこまでする価値はまったくないと思います。

0Like

Comments

  1. @itagagaki

    Questioner

    なるほど、ありがとうございます。

    個々のウィンドウにプロセスアイコンを設定する方がよっぽど楽

    いえ、個々のウィンドウのアイコンを時と場合によって自由に変えることができることとしているのが制作中のアプリの仕様ですので、ウィンドウにプロセスアイコンを設定すればよいということではないのです。(それらウィンドウに設定したアイコンはタスクバーやAlt-Tab等の表示に反映されます)。

    VisualStudioデバッガの仕様であり、回避することはできません。

    それを回避したいわけではないのです。

    OSから送られるメッセージの多くはプロセスではなくウィンドウ(hWnd)に対して送られるので、デバッガはそのメッセージを横取りするための偽のウィンドウを作り、そこで受け取ったメッセージをデバッガの情報として記録した後で本来受け取るべきターゲットプロセスのウィンドウに再送信することでターゲットプログラムはデバッガがない時と変わらない動作となります。

    ターゲットプログラムの動作はデバッガがない時と変わらないですが、なぜ音量ミキサーの挙動が変わるのか、この理屈では説明がつかないので、なおもって謎ですね…。

    私も、そんなところだろうなと思って、偽のウィンドウやハンドラーを作ってウィンドウアイコン取得メッセージが来たら偽のアイコンを返すようにしてみたりしたのですが(WPFアプリでそんなことをするのはめっちゃ面倒くさかったですが)、そうすると、まあ当然ながら、ウィンドウアイコンが表示されるべきところでも偽のアイコンになってしまうので、意味のないことでした)。

    音量ミキサーについては(中略)そこから得られるアイコン情報はウィンドウのアイコンとなるのは当然で

    そのような挙動になるのは、知る限り「音量ミキサー」のみで、同じような機能を有する3rdパーティユーティリティー(たとえば「Volumey」や「EarTrumpet」)ではアプリアイコンを表示してくれるのです。「音量ミキサー」だけ困ったことになっているわけです。「音量ミキサー」の中身は当然まったくわからないし、どうやら外から何かできるような技も無さそうなので、半ば諦めていました(ユーザーには音量ミキサーの代わりにVolumeyかEarTrumpetを使うことを勧めることにして)。

    しかし、ふとVisual Studioで制作中のアプリをデバッグ実行すると音量ミキサーの挙動が変わった(しかも逆に、希望する挙動になった)ので、もしかしたらそこを突き詰めたら問題を回避できる可能性がある(無駄で邪道なことであっても回避策として有り得る)かもなと思って、少しでも手がかりが欲しくて質問させていただいたわけです。

  2. なるほど。
    音量ミキサー周りはまったくわからないので、私にはこれ以上言えることはなさそうです。

  3. @itagagaki

    Questioner

    ややこしくて申し訳ありませんでした。

Your answer might help someone💌