Программирование на Visual C++. Архив рассылки
Шрифт:
A. Для создания окна сплиттера в MFC служит класс CSplitterWnd. Этот класс предоставляет функции для создания вида (CreateView) и удаления вида DeleteView) в заданной панели, но не предоставляет функции, которая позволила бы перенести вид из одной панели в другую. Чтобы проделать это вручную, нужно понимать, каким образом связаны объект класса CSplitterWnd и объекты дочерних видов CView.
CSplitterWnd может иметь не более 16 панелей по горизонтали и столько же по вертикали. Таким образом, он может сожержать не более 256 панелей. Каждой панели соответствует уникальный идентификатор, который и назначается тому виду, который в этой панели находится. Отображение координат панели на её идентификатор выполняет функция int CSplitterWnd::IdFromRowCol(int row, int col);
На самом деле после целой серии ASSERT'ов она просто возвращает значение
где AFX_IDW_PANE_FIRST —
Это подсказывает простой способ перемещения вида из одной панели в другую: нужно всего лишь подменить его идентификатор, после чего вызвать CSplitterWnd::RecalcLayout для обновления содержимого сплиттера. Если изначально вид не являлся дочерним окном сплиттера и требуется поместить его в одну из панелей, то необходимо также поменять ему родителя с помощью функции CWnd::SetParent. Таким образом функция, вставляющая вид в заданную панель, может выглядеть примерно так:
Аналогичным образом вид переносится "в юрисдикцию" главного окна приложения, порождённого от CFrameWnd:
Имея в руках эти две функции, можно без труда решить поставленную задачу, располагая виды как на рис. A, B, C или любым другим способом. Ещё раз замечу, что после всех перемещений необходимо вызывать CSplitterWnd::RecalcLayout и CFrameWnd::RecalcLayout.
Alexander Shargin, воистину герой сегодняшнего выпуска, в письме с ответом на предыдущий вопрос также недоумевал по поводу вопроса прошлого выпуска, где требовалось минимизировать CPropertySheet:
…Честно говоря, я не понимаю, о чём идёт речь. Я буду очень вам признателен, если вы объясните мне, в чём проблема. Дело в том, что я создал визардом приложение на базе диалога, а затем просто заменил диалог на объект класса CMySheet, порождённого от CPropertySheet. После чего добавил пару вкладок (типа CPropertyPage) и вызов ModifyStyle(0, WS_MINIMIZEBOX) в обработчике OnCreate. В результате этих несложных операций получилось приложение, главное окно которого без проблем сворачивается на панель задач.
Я посмотрел приаттаченный им проект и убедился, что Александр совершенно прав. После чего я сам проделал то же самое с новым проектом, и получил такой же результат. Итак, вся загвоздка была в том, что ModifyStyle нужно было вызывать не из OnInitDialog, а из OnCreate!
После этого я задумался, откуда же у класса CPropertySheet вообще есть метод OnInitDialog, ведь сам класс является прямым наследником CWnd. Оказалось, что этот метод, наряду с DoModal, был добавлен туда искусственно, чтобы обращение с классом напоминало обращение с CDialog. Не знаю, почему бы Microsoft просто не сделать CPropertySheet наследником CDialog, но наверное у них были свои причины (хотя здесь можно и посомневаться ;)
Я переслал письмо Александра человеку, задавшему вопрос, и получил от него положительный ответ – у него тоже все заработало.
Вот, оказывается, как просто открывался ларчик! Не надо было перехватывать WM_NCLBUTTONDOWN, не нужно было делать callback функцию… (решение автора вопроса)…
И еще – насчет минимизации в левый нижний угол – видимо это был частный случай поведения, вызванный моими манипуляциями со стилями ;-)
Напоследок хочу процитировать Win32 Q&A из MSDN, чтобы абсолютно точно уяснить для всех
Правила эти довольно просты, хотя и не очень хорошо документированы. Когда вы создаете окно, Windows проверяет его расширенный
Можно создать окно, не имеющее ни один из этих стилей. Тогда кнопка выводится только в том случае, если у окна нет родителей.
И последнее замечание: прежде чем тестировать что-либо из вышеописанного, панель задач проверяет, установлен ли стандартный стиль видимости WS_VISIBLE. Если нет, то окно спрятано, и показывать кнопку нет никакого смысла. Стили WS_EX_APPWINDOW, WS_EX_TOOLWINDOW и информация о принадлежности окна проверяются ТОЛЬКО при установленном WS_VISIBLE.
Q. У меня программа с использованием MFC и Doc/View. Я вставил RichEditCtrl во вью. (2-ой версии). Установил шрифт с помощью сообщения SetCharFormat. Внимание, вопрос: почему если я ввожу текст с клавиатуры и использую ReplaceText функцию (не сообщение!) фонты различные? Вроде это сообщение не менялось у второй версии. Заранее спасибо за ответ.
Это все на сегодня. Пока!
Программирование на Visual C++
Выпуск №18 от 7 октября 2000 г.
Приветствую вас!
После публикации статьи про CPropertySheet меня обвинили в чрезмерной ориентированности на начинающих программистов (на самом деле было использовано другое слово). Так что материал этого выпуска как раз для чуть-чуть более "продвинутых".
И еще, хочу чтобы вы приняли к сведению: я отвечаю НЕ НА ВСЕ приходящие письма. Когда вы присылаете мне вопрос, не нужно рассчитывать на то, что я обязательно на него отвечу или помещу в рубрику "В поисках истины".
В обязательном порядке это делается только для тех читателей, кто до этого сам присылал статьи или ответы на вопросы (и которые были впоследствии опубликованы в рассылке).
Сегодня я хочу представить вашему вниманию статью – перевод из MSJ C++ Q&A, которую прислал Илья Простакишин.
Проблема в том, что OnIdle работает нормально только в приложениях document/view, что не скажешь о приложениях dialog-based. Функция CApp::InitInstance вызывает dlg.DoModal, которая в свою очередь вызывает CWnd::RunModalLoop, а та никогда не обращается к OnIdle. Кажется, что можно производить фоновую обработку посредством WM_ENTERIDLE, но это сообщение направляется только родительскому окну диалога, которого в нашем случае просто не существует. Как решить эту проблему?
Модальные диалоги на самом деле лишь имитируются в MFC. Когда вы вызываете CDialog::DoModal, MFC не вызывает ::DialogBox, как можно было ожидать; вместо этого вызывается ::CreateDialogIndirect, затем модальное поведение имитируется путем блокировки родительского окна и запуска своего собственного цикла обработки сообщений. По существу, тоже самое делает функция ::DialogBox. Тогда зачем изобретать велосипед? А дело в том, что теперь MFC имеет свой собственный цикл, в то время как раньше он "прятался" внутри функции API ::DialogBox. Это позволяет MFC обрабатывать сообщения посредством обычных потоков MFC (CWinThread::PumpMessage), что и делается с другими типами окон. В результате вы можете переопределять CWnd::PreTranslateMessage для модальных диалогов – например, для реализации "горячих" клавиш. Ранние версии MFC позволяли реализовывать свою собственную функцию PreTranslateMessage для модального диалога. Но толку от этого было мало, ведь она все равно никогда не вызывалась, т.к. CDialogDoModal напрямую обращалась к ::DialogBox. При этом управление в программу не возвращалось, пока один из обработчиков сообщений вашего диалога не вызывал EndDialog. По этой же причине была невозможна обработка интервала ожидания.
Вместо этого Windows имеет свой собсвенный механизм, WM_ENTERIDLE, предназначенный для обработки интервала ожидания в модальных диалогах. После обработки одного или нескольких сообщений, если в очереди больше ничего нет, Windows автоматически посылает WM_ENTERIDLE окну-владельцу модального диалога или меню. Работает это только в модальных диалогах. Поскольку MFC теперь использует немодальные диалоги в качестве модальных, WM_ENTERIDLE посылается библиотекой "вручную", чтобы самостоятельно имитировать модальные диалоги, но, опять же, только если имеется родительское окно.