Как сделать так, чтобы окно всегда оставалось наверху в .Net?


95

У меня есть приложение winforms на C #, которое запускает макрос в другой программе. Другая программа будет постоянно выскакивать из окон и в целом заставлять вещи выглядеть, если не сказать точнее, сумасшедшими. Я хочу реализовать кнопку отмены, которая остановит процесс, но я не могу заставить окно оставаться наверху. Как мне это сделать на C #?

Изменить: я пробовал TopMost = true; , но другая программа продолжает открывать свои окна поверх. Есть ли способ отправлять мое окно наверх каждые n миллисекунд?

Изменить: Я решил это, добавив значок на панели задач, который отменит процесс, дважды щелкнув по нему. Значок на панели задач не закрывается. Спасибо всем, кто откликнулся. Прочитал статью о том, почему нет окна "супер-поверх" ... по логике не работает.


63
Да, установите таймер на каждые несколько миллисекунд, который установит для вашего Form.TopMost значение true. Затем, чтобы было интересно, при загрузке "сумасшедшей" программы проиграйте аудиоклип из Mortal Kombat "FIGHT!" :-P
BFree

2
Вы могли подумать, что ваш комментарий был веселым, вы могли подумать, что можете высмеять плохую практику. Моя проблема заключалась в создании контекстного меню, которое плавает над формой с помощью flowlayoutpanel. Панель flowlayout можно прокручивать, только если вы вызываете ее метод Activate (), Focus () НЕ достаточен в определенных обстоятельствах. Вы просто не сможете его прокрутить. Это отвлекает внимание от контекстного меню, даже если оно имеет эксклюзивное значение topmost = true! Любой здравомыслящий человек знает, что это благочестивая практика - позволить вашим приложениям winform работать в режиме MTAThread и дать каждой форме свой собственный поток, что упрощает решение:
Traubenfuchs

1
Вот дьявол: pastebin.com/sMJX0Yav Он работает безупречно, без мерцания, и сна (1) достаточно, чтобы он не истощал серьезную производительность. Кто все равно смотрит в свой диспетчер задач, пока он фокусируется на контекстном меню? Как только контекстное меню закрывается, оно, надеюсь, сталкивается с пустым обработчиком исключений и умирает. Тем не менее, вы можете встроить перерыв isDisposed.
Traubenfuchs

Я только что опубликовал свое решение этой проблемы здесь: stackoverflow.com/questions/2546566/…
kfn

@Traubenfuchs Это не удастся из-за исключения межпотоковой операции. Это должно сработать.
mekb

Ответы:


172

Form.TopMost будет работать, если другая программа не создает самые верхние окна.

Невозможно создать окно, которое не закрывается новыми самыми верхними окнами другого процесса. Раймонд Чен объяснил почему.


11
Если другие новички увидят это в 2016 году и позже, попробуйтеForm.ActiveForm.TopMost
Devil's Advocate

1
@ScottBeeson: Это хорошо. Обновленная информация очень необходима в этом динамичном мире техно. Благодарность:).
Sandeep Kushwah

49

Я пытался сделать свое приложение WinForms «Всегда наверху», но установка «TopMost» ничего не дала мне. Я знал, что это возможно, потому что WinAmp делает это (вместе с множеством других приложений).

Что я сделал, так это позвонил в "user32.dll". Я не сомневался в этом, и он отлично работает. Во всяком случае, это вариант.

Сначала импортируйте следующее пространство имен:

using System.Runtime.InteropServices;

Добавьте несколько переменных в объявление вашего класса:

private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const UInt32 SWP_NOSIZE = 0x0001;
private const UInt32 SWP_NOMOVE = 0x0002;
private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;

Добавьте прототип для функции user32.dll:

[DllImport("user32.dll")] 
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

Затем в вашем коде (я добавил вызов в Form_Load ()) добавьте вызов:

SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);

Надеюсь, это поможет. Справка


2
Это работает не только для приложений WinForms, но и для окон консоли . Хорошая находка!
rojo

Приятно, могу подтвердить, что это работает. Но как я смогу изменить его, чтобы он не был самым лучшим? есть ли флаг HWND_BOTTOMMOST, которым вы можете поделиться?
Марк

Хороший вопрос, если вы хотите переключаться между этой самой верхней способностью и поведением по умолчанию (например, у вас есть флажок «Всегда сверху» для поведения окна). Я предполагаю, что, возможно, с соответствующими флагами это будет возможно. Если бы у вас были правильные флаги, описывающие поведение окна по умолчанию (скажем, SWP_DEFAULT = 0x0003), то вы могли бы просто снова вызвать SetWindowPos () с этими флагами. Я просто не уверен; Я не разбирался в этом. Удачи, если вы это сделаете, и если кто-то сделает, пожалуйста, добавьте это сюда!
clamum

Это не работает в обычном полноэкранном режиме игры
Саджита Ратнаяке

2
@Mark Да, есть флаг HWND_NOTOPMOST (= -2). См. Docs.microsoft.com/en-us/windows/win32/api/winuser/…
Kevin

23

Если под словом «сойти с ума» вы имеете в виду, что каждое окно продолжает отнимать фокус у другого, TopMost не решит проблему.

Вместо этого попробуйте:

CalledForm.Owner = CallerForm;
CalledForm.Show();

Это покажет «дочернюю» форму, не отвлекая внимание. Дочерняя форма также останется поверх своей родительской, даже если родительская форма активирована или сфокусирована. Этот код работает легко, только если вы создали экземпляр дочерней формы из формы владельца. В противном случае вам, возможно, придется установить владельца с помощью API.


1
Большое спасибо, я использовал именно это, и он отлично работал!
AvetisG

1
Спасибо .. именно то, что я искал
Самира Кумарасинга

Установка CalledForm.Ownerна себя ( CalledForm) вызовет System.ArgumentException: «Была сделана циклическая ссылка на элемент управления. Элемент управления не может принадлежать самому себе ».
mekb

2
Вот почему вы используете CallerForm вместо CalledForm :)
Джеспер

16

Установить Form.TopMost


Я пробовал, это ... мне нужно постоянно это делать? «Безумная программа» вступает в силу немедленно ...
июл

2
Нет - если вы установите form.TopMost = true, все должно работать. В "сумасшедшей" программе диалоговые окна также должны быть установлены на TopMost, и в этом случае вы не можете это изменить.
Рид Копси,

Нечестный бой. Спасибо.
jle

11

У меня был кратковременный перерыв в 5 минут, и я забыл указать форму полностью следующим образом:

  myformName.ActiveForm.TopMost = true;

Но на самом деле я хотел ЭТОГО!

  this.TopMost = true;

Сработал идеально для меня. если (checkBox1.Checked == true) {this.TopMost = true; } else {this.TopMost = false; }
yosh

6

Установите для .TopMostсвойства формы значение true.

Вероятно, вы не захотите оставлять его так все время: установите его при запуске внешнего процесса и верните его обратно, когда он завершится.


5

Я решил эту проблему, сделав значок в системном трее с возможностью отмены.


5

Следующий код заставляет окно всегда оставаться наверху, а также делает его безрамочным.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace StayOnTop
{
    public partial class Form1 : Form
    {
        private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
        private const UInt32 SWP_NOSIZE = 0x0001;
        private const UInt32 SWP_NOMOVE = 0x0002;
        private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        public Form1()
        {
            InitializeComponent();
            FormBorderStyle = FormBorderStyle.None;
            TopMost = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            SetWindowPos(this.Handle, HWND_TOPMOST, 100, 100, 300, 300, TOPMOST_FLAGS);
        }

        protected override void WndProc(ref Message m)
        {
            const int RESIZE_HANDLE_SIZE = 10;

            switch (m.Msg)
            {
                case 0x0084/*NCHITTEST*/ :
                    base.WndProc(ref m);

                    if ((int)m.Result == 0x01/*HTCLIENT*/)
                    {
                        Point screenPoint = new Point(m.LParam.ToInt32());
                        Point clientPoint = this.PointToClient(screenPoint);
                        if (clientPoint.Y <= RESIZE_HANDLE_SIZE)
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)13/*HTTOPLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)12/*HTTOP*/ ;
                            else
                                m.Result = (IntPtr)14/*HTTOPRIGHT*/ ;
                        }
                        else if (clientPoint.Y <= (Size.Height - RESIZE_HANDLE_SIZE))
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)10/*HTLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)2/*HTCAPTION*/ ;
                            else
                                m.Result = (IntPtr)11/*HTRIGHT*/ ;
                        }
                        else
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)16/*HTBOTTOMLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)15/*HTBOTTOM*/ ;
                            else
                                m.Result = (IntPtr)17/*HTBOTTOMRIGHT*/ ;
                        }
                    }
                    return;
            }
            base.WndProc(ref m);
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style |= 0x20000; // <--- use 0x20000
                return cp;
            }
        }
    }
}

Согласитесь с Алексаном - что делает вашу программу лучшей? Похоже, что на самом деле это просто оператор «topmost = true», который во многих случаях не работает. Весь остальной код на самом деле не решает проблему.
Fhaab

4

Какое еще приложение вы пытаетесь скрыть от видимости? Вы исследовали другие способы достижения желаемого эффекта? Пожалуйста, сделайте это, прежде чем подвергать своих пользователей такому мошенническому поведению, которое вы описываете: то, что вы пытаетесь сделать, скорее похоже на то, что некоторые непослушные сайты делают с окнами браузера ...

По крайней мере, постарайтесь придерживаться правила наименьшего сюрприза . Пользователи ожидают, что смогут сами определять z-порядок большинства приложений. Вы не знаете, что для них наиболее важно, поэтому, если вы что-то измените, вам следует сосредоточиться на том, чтобы подталкивать другое приложение за всем, а не продвигать свое собственное.

Это, конечно, сложнее, поскольку в Windows нет особо сложного оконного менеджера. Напрашиваются два подхода:

  1. перечисление окон верхнего уровня и проверка того, какому процессу они принадлежат , отбрасывая их z-порядок, если это так . (Я не уверен, существуют ли методы фреймворка для этих функций WinAPI.)
  2. Возиться с разрешениями дочернего процесса, чтобы предотвратить его доступ к рабочему столу ... но я бы не стал пробовать это до тех пор, пока другой подход не потерпит неудачу, поскольку дочерний процесс может оказаться в состоянии зомби, требуя взаимодействия с пользователем.

4

Почему бы не сделать вашу форму диалоговым окном:

myForm.ShowDialog();

1
Да! Это то, что я хотел. Настройка TopMost = trueзаставила мою форму работать поверх всего, включая хром, хотя на самом деле это всего лишь окно настроек, и мне это нужно было поверх основной формы. Престижность вам, интернет-человек.
MDMoore313,

3

Вот эквивалент SetForegroundWindow:

form.Activate();

Я видел, как люди делали такие странные вещи, как:

this.TopMost = true;
this.Focus();
this.BringToFront();
this.TopMost = false;

http://blog.jorgearimany.com/2010/10/win32-setforegroundwindow-equivalent-in.html


Что, если я не хочу, чтобы мое окно было активным, я просто хочу, чтобы оно было наверху (информативным, а не интерактивным)? Я спрашиваю только потому, что на самом деле выдача "topmost = True" не работает в моем случае (это работает в системах, а не в других).
Fhaab

Нашел, что это работает для нас: this.Show(); this.Activate(); this.BringToFront(); Но этот ответ привел нас к этому решению. Благодарность!
jibbs

1

Я знаю, что это старый, но я не видел этого ответа.

В окне (xaml) добавьте:

Deactivated="Window_Deactivated"

В коде для Window_Deactivated:

private void Window_Deactivated(object sender, EventArgs e)
    {
        Window window = (Window)sender;
        window.Activate();
    }

Так ваше окно останется наверху.


1
Вы не видели этого ответа, потому что вопрос касается winform.
Kinetic

0

Основываясь на ответе Clamum и комментарии Кевина Вуйломье о другом флаге, отвечающем за поведение, я сделал этот переключатель, который переключается между верхним и не верхним слоем нажатием кнопки.

private void button1_Click(object sender, EventArgs e)
    {
        if (on)
        {
            button1.Text = "yes on top";
            IntPtr HwndTopmost = new IntPtr(-1);
            SetWindowPos(this.Handle, HwndTopmost, 0, 0, 0, 0, TopmostFlags);
            on = false;
        }
        else
        {
            button1.Text = "not on top";
            IntPtr HwndTopmost = new IntPtr(-2);
            SetWindowPos(this.Handle, HwndTopmost, 0, 0, 0, 0, TopmostFlags);
            on = true;
        }
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.