Все выпуски  

Visual C++ - расширенное программирование Компонент Splash Screen


Информационный Канал Subscribe.Ru

Visual C++ - расширенное программирование

Выпуск № 10
Cайт : SoftMaker.com.ru
Архив рассылки : SoftMaker.com.ru
Количество подписчиков : 2002

В этом выпуске

От ведущего


  Здравствуйте, уважаемые подписчики !

  Как всегда рад приветствовать вас на страницах этой рассылки. У меня сегодня много новостей, поэтому позволю себе сделать вступление немного больше чем обычно.

  Прежде всего, хочу извиниться за небольшую задержку в выпуске рассылки. Но к тому были объективные причины. Главная из них состоит в том, что, как вы могли заметить, данный выпуск рассылки в некотором роде юбилейный (он десятый по счету), а поэтому готовился долго и особо тщательно. В этом вы сможете убедиться сами, прочитав номер.
  Действительно, в этом выпуске материала больше чем обычно. Кроме традиционной статьи про создание файлового менеджера (которая также весьма отличается от предыдущих - будет разработана не просто еще одна составляющая программы, а целый компонент, который можно будет применять в других проектах), сегодня здесь будет опубликована совершенно новая статья, которую первыми прочтете вы – подписчики (на сайте рассылки она появится только через несколько дней).
  Также я позволил себе подвести небольшие итоги по вышедшим выпускам в конце номера.

  А теперь, собственно, новости.

  Новость первая, очень радостная.
 Рассылку перевели в основную категорию, и теперь ее можно найти в основном каталоге Subscribe в категории серебряных (!) рассылок.
 Страница каталога рассылки : http://subscribe.ru/catalog/comp.soft.prog.qandacpp

  Новость вторая, не менее радостная.
 Сайт переехал на другое место (на более качественный коммерческий хостинг). Теперь он будет доступен практически всегда, а сбоев в работе наблюдаться не будет.
 Новый адрес сайта http://SoftMaker.com.ru.
 Там вы сможете найти все новые статьи и архив рассылки.
 Также на сайте работает форум ( http://forum.SoftMaker.com.ru ) для обсуждения вопросов программирования.
 Форум только открылся, поэтому приглашаю всех туда для обмена опытом.

  Новость третья.
  Для многих она будет очень интересна. Вы, наверно, знаете, что не так давно службой Subscribe был открыт сервис дискуссионных листов. Дискуссионные листы – это рассылки, содержимое которых формируется самими подписчиками. То есть вы можете написать письмо по некоторому адресу, и почти сразу всем подписчикам будет разослан очередной выпуск дискуссионного листа с вашим письмом. Подписчики смогут ответить на ваше сообщение. Таким образом, можно вести обсуждение различных вопросов по почте сразу со всеми участниками дискуссионного листа.

  В рамках проекта SoftMaker был создан дискуссионный лист для программистов « Программирование. Форум !!!», в котором можно задавать вопросы, получать ответы, вести обсуждение любых вопросов по программированию. В листе очень просто принять участие, подписавшись на него.

  Программирование. Форум !!!    

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

  И, наконец, новость последняя.
Число подписчиков перевалило за 2000 !!! Что очень радует.

  Как всегда, Вы можете отправить свои пожелания по поводу рассылки и сайта по этому адресу.

  Если вы хотели бы опубликовать статью в данной рассылке, либо вести здесь какой либо раздел, то также можете писать мне.

Искренне Ваш. С уважением, Вахтуров Виктор.  

MFC - простое и сложное [Создание файлового менеджера (часть 5)].

Часть 5.
Компонент Splash Screen.
Пролог.

  Давайте сегодня мы немного отвлечемся от совершенствования интерфейса файлового менеджера. Сегодня мы напишем компонент Splash Screen.

  Для чего он нужен ?

  Вы, наверно, не раз видели программы, время загрузки которых достаточно велико. И могли заметить, что в процессе запуска некоторых программ перед появлением основного окна появляется окно заставки (обычно с логотипом программы и/или фирмы-производителя). Такое окно (его и принято называть Splash Screen) может просто появляться и исчезать (после появления на экране главного окна приложения), а может и отображать процесс загрузки программы (как это, например, сделано в Adobe Photoshop) и даже содержать элементы анимации.

  Вот такой компонент мы и будем создавать.

Существующие решения.

  Но давайте подумаем. Неужели такой распространенный компонент нигде не реализован ? Наверняка уже есть готовые решения, которые можно использовать. Это действительно так. В галерее компонентов Visual С++ есть готовый компонент Splash Screen, который при желании очень просто подключить к проекту. Достаточно выбрать в меню Project/Add To Project/Components and Controls, в появившемся окне войти в папку Visual C++ Components и выбрать из списка “Splash screen”. После этого появится окно сообщения с вопросом о добавлении компонента. Нажав OK, и введя идентификатор битовой карты в следующем диалоге можно легко получить желаемый результат.

  Главным недостатком этого компонента является то, что само окно Splash Screen создается в основном потоке приложения. А это значит, что если инициализировать заставку в начале функции InitInstance класса приложения, то пока происходит инициализация (загрузка динамических библиотек, создание главного окна приложения, и.т.д. ) окно заставки не обработает ни одного сообщения (оно не будет перерисовываться, перемещаться, реагировать на клики мыши). Также будет невозможно обрабатывать сообщения WM_TIMER (если мы хотим сделать в окне простую анимацию, то ее проще всего синхронизировать по времени сообщениями таймера).

  Таким образом, стандартный компонент совсем не удовлетворит наших потребностей.

Создание своего компонента Splash Screen.

  Итак, давайте разработаем более продвинутый компонент, который будет выглядеть более интересно (элегантней) чем стандартный, обладать более широкими возможностями (например, в окно заставки можно будет выводить информацию о ходе процесса инициализации приложения), будет простым в использовании и легко модифицируемым. У него также не должно быть проблем с перерисовкой, описанных выше.

  Элегантность и эстетичность окну компонента можно придать следующим образом :

  1. Подобрать приятный цвет фона и текста.
  2. Создать не стандартное (четырехугольное) окно, а окно произвольной формы.
  3. Расположить в окне дополнительные (декоративные) элементы оформления.
  4. Добавить в окно анимацию.

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


  Для придания более выраженного стиля компоненту в качестве дополнительных элементов оформления также можно использовать области с градиентной заливкой.

  Как видно, компонент получится не совсем уж и простым. Подумаем, как же лучше реализовать поставленные задачи.

  Сразу определимся с главным – с окном заставки. Исходя из требования легкости модификации, лучше всего реализовать это окно как диалог – шаблон диалога легко создать в редакторе ресурсов Visual C++, а также редактировать его впоследствии. В этом случае также легко решается проблема вывода текстовой информации – можно ввести в шаблон диалога элемент управления ‘статический текст’, в который и помещать нужный текст.
  Вывод анимации и областей с градиентной заливкой можно было бы реализовать путем простого рисования необходимых изображений на поверхности окна заставки, но исходя, опять же, из соображений гибкости компонента лучшим решением будет создание своего элемента управления, способного отображать градиенты, и воспроизводить анимацию градиентных областей.

  Для избежания проблем отрисовки окна Splash Screen во время выполнения длительных задач в основном потоке приложения, необходимо инициализировать его в отдельном потоке (так называемом потоке пользовательского интерфейса - user-interface thread).

  Таким образом приходим к тому, что наш компонент будет состоять из трех классов : класса потока пользовательского интерфейса, класса диалогового окна заставки (само окно Splash Screen) и класса элемента управления, отображающего градиентные области и создающего анимацию градиентных областей.

  Далее – обо всем по порядку, но сначала создадим проект. Назовем проект, например, SplashDemo. Проект создадим как однооконное приложение без поддержки архитектуры документ-облик (она нам совсем не нужна). На самом деле нам абсолютно все равно, какое будет главное окно приложения – мы создаем компонент, не зависящий от проекта в котором он используется.
Выбор каркаса приложения я также объясню чуть позже (далее будет описан небольшой трюк, позволяющий отобразить диалоговое окно в главном окне-рамке приложения, что во многих случаях гораздо удобнее, чем создавать приложение с использованием класса CFormView).

Класс элемента управления для отображения градиентных заливок.

  Созданием данного класса займемся в первую очередь (на самом деле класс писался, отлаживался и тестировался в рамках небольшого приложения, созданного именно для этого, но опишу здесь так, как будто мы создаем этот класс в нашем демонстрационном проекте).
  Как я уже сказал, данный класс будет представлять собой элемент управления, который способен отображать градиентную заливку, как в статическом состоянии, так и в движении.
  За основу нашего элемента управления выберем элемент управления Windows ‘статический текст’ (оконный класс ‘Static’) и, соответственно, класс CStatic библиотеки классов MFC. Такой выбор определяется тем, что данный элемент управления поддерживается редактором ресурсов Visual C++ и может быть легко помещен в шаблон диалога. В шаблоне диалога также можно выбрать для данного элемента управления режим его отображения как закрашенного прямоугольника (такому режиму соответствуют оконные стили данного элемента управления SS_BLACKRECT и SS_WHITERECT), что позволит точно задавать его размеры.
  Идея реализации отображения градиентов нашим элементом управления весьма проста. Изображение будет подготавливаться и хранится в аппаратно-зависимом растре, а при обработке сообщения WM_PAINT – просто выводиться на поверхность элемента. При воспроизведении анимации, изображение в растре будет “сдвигаться” при помощи бинарной растровой операции копирования (при этом растр, конечно же, будет использоваться в качестве поверхности совместимого контекста устройства), а вертикальная линия (нулевой столбец растра) будет “дорисован”.

  Итак, создадим класс, наследованный от класса CStatic библиотеки. Назовем его CGradientStatic.

  В качестве параметров для рисования области градиентной заливки элементу управления будут задаваться : начальный цвет заливки, конечный цвет заливки и количество полупериодов повторения заливки при распределении по длине элемента.
Эти параметры будут храниться в переменных – компонентах класса CGradientStatic :
COLORREF m_clrColorBeg, COLORREF m_clrColorEnd и int m_nHalfPeriodCount соответственно.
Они могут быть установлены посредством вызова функции–члена класса :
CGradientStatic::SetGradient(COLORREF clrBegColor, COLORREF clrEndColor, int nHalfPeriodCount)
Основные алгоритмы рисования градиентных областей содержатся в функциях :
CGradientStatic::UpdateGradient(BOOL bIfNotCreated) и
CGradientStatic::DrawLineIncColor(CDC *pDC, int nPos, int nHeight)

Приведу здесь их код :

 
 
BOOL CGradientStatic::UpdateGradient(BOOL bIfNotCreated) 
{ 
 if(::IsWindow(GetSafeHwnd())) 
 { 
  if((m_bmp.GetSafeHandle() == NULL) || !bIfNotCreated) 
  { 
   DeleteBitmap(); 
 
   CRect rcClient; 
 
   GetClientRect(rcClient); 
 
   int nWidth = rcClient.Width(); 
   int nHeight = rcClient.Height(); 
 
   m_kR = 1; 
   m_kG = 1; 
   m_kB = 1; 
 
   m_fColorR = double(GetRValue(m_clrColorBeg)); 
   m_fColorG = double(GetGValue(m_clrColorBeg)); 
   m_fColorB = double(GetBValue(m_clrColorBeg)); 
 
   m_fDeltaR = double(m_nHalfPeriodCount) * 
       double(GetRValue(m_clrColorEnd) - 
       GetRValue(m_clrColorBeg)) / double(nWidth); 
 
   m_fDeltaG = double(m_nHalfPeriodCount) * 
       double(GetGValue(m_clrColorEnd) - 
       GetGValue(m_clrColorBeg)) / double(nWidth); 
 
   m_fDeltaB = double(m_nHalfPeriodCount) * 
       double(GetBValue(m_clrColorEnd) - 
       GetBValue(m_clrColorBeg)) / double(nWidth); 
 
   CClientDC dc(this); 
 
   CDC dcMem; 
 
   dcMem.CreateCompatibleDC(&dc); 
 
   m_bmp.CreateCompatibleBitmap(&dc, nWidth, nHeight); 
    
   CBitmap *pOldBitmap = dcMem.SelectObject(&m_bmp); 
 
   for(int nPosColor = 0; nPosColor < nWidth; nPosColor++) 
    DrawLineIncColor(&dcMem, nPosColor, nHeight); 
 
   dcMem.SelectObject(pOldBitmap); 
  } 
  else 
   return FALSE; 
 
  if(m_bEnableAnimation && !m_nTimerID) 
   m_nTimerID = SetTimer(1, m_nTimerElapse, NULL); 
  else if(m_nTimerID && !m_bEnableAnimation) 
  { 
   KillTimer(m_nTimerID); 
   m_nTimerID = 0; 
  } 
 } 
 
 return TRUE; 
} 
 
void CGradientStatic::DrawLineIncColor(CDC *pDC, int nPos, int nHeight) 
{ 
 if(::IsWindow(GetSafeHwnd())) 
 { 
  CPen pen, *pOldPen; 
 
  pen.CreatePen(PS_SOLID, 1, RGB(int(m_fColorR), int(m_fColorG), 
     int(m_fColorB))); 
 
  pOldPen = pDC->SelectObject(&pen); 
  pDC->MoveTo(nPos, 0); 
  pDC->LineTo(nPos, nHeight); 
  pDC->SelectObject(pOldPen); 
  pen.DeleteObject(); 
 
  m_fColorR += (m_fDeltaR * double(m_kR)); 
  m_fColorG += (m_fDeltaG * double(m_kG)); 
  m_fColorB += (m_fDeltaB * double(m_kB)); 
 
  if(((m_fColorR - double(GetRValue(m_clrColorEnd))) * 
    double(sgn(m_fDeltaR))) > 0.0) 
  { 
   m_kR = -1; 
   m_fColorR = double(GetRValue(m_clrColorEnd)); 
  } 
  else if(((m_fColorR - double(GetRValue(m_clrColorBeg))) * 
    double(sgn(m_fDeltaR))) < 0.0) 
  { 
   m_kR = 1; 
   m_fColorR = double(GetRValue(m_clrColorBeg)); 
  } 
 
  if(((m_fColorG - double(GetGValue(m_clrColorEnd))) * 
    double(sgn(m_fDeltaG))) > 0.0) 
  { 
   m_kG = -1; 
   m_fColorG = double(GetGValue(m_clrColorEnd)); 
  } 
  else if(((m_fColorG - double(GetGValue(m_clrColorBeg))) * 
    double(sgn(m_fDeltaG))) < 0.0) 
  { 
   m_kG = 1; 
   m_fColorG = double(GetGValue(m_clrColorBeg)); 
  } 
 
  if(((m_fColorB - double(GetBValue(m_clrColorEnd))) * 
    double(sgn(m_fDeltaB))) > 0.0) 
  { 
   m_kB = -1; 
   m_fColorB = double(GetBValue(m_clrColorEnd)); 
  } 
  else if(((m_fColorB - double(GetBValue(m_clrColorBeg))) * 
    double(sgn(m_fDeltaB))) < 0.0) 
  { 
   m_kB = 1; 
   m_fColorB = double(GetBValue(m_clrColorBeg)); 
  } 
 } 
} 

  Идея проста. Красная, зеленая и синяя составляющие для текущей вертикальной линии заливки хранятся в переменных-компонентах класса : m_fColorR, m_fColorG, m_fColorB соответственно.
  Функция DrawLineIncColor рисует одну вертикальную линию градиента на переданном в нее по указателю контексте устройства (в котором выбран аппаратно-зависимый растр, дескриптор которого инкапсулируется в объекте класса CBitmap m_bmp). После рисования линии в этой функции происходит увеличение или уменьшение (в зависимости от переменных m_kR, m_kG, m_kB) значений m_fColorR, m_fColorG, m_fColorB.
  Переменные m_fColorR, m_fColorG, m_fColorB инициализируются в функции UpdateGradient значениями цветовых составляющих переменной m_clrColorBeg. Там же инициализируются значения переменных m_kR, m_kG, m_kB, а также переменных m_fDeltaR, m_fDeltaG, m_fDeltaB, значения которых представляют собой величины изменения каждой цветовой составляющей при последовательном рисовании градиента.
  Таким образом функция CGradientStatic::UpdateGradient выполняет первоначальное рисование градиентной области.
  Включение режима анимации производится посредством вызова функции :

 
 
CGradientStatic::EnableAnimation(BOOL bEnable, UINT nElapse) 

  Первый параметр указывает на то, надо ли включить или отключить анимацию. Второй параметр задает период времени (в миллисекундах) между “смещением” градиентной области на один пиксель вправо.
  Функция EnableAnimation запускает таймер, в обработчике сообщений которого и происходит “сдвиг” изображения градиентной заливки и “дорисовывание” еще одной линии :

 
 
void CGradientStatic::OnTimer(UINT nIDEvent)  
{ 
 if(m_nTimerID && (nIDEvent == m_nTimerID)) 
 { 
  MSG Msg; 
 
  while(::PeekMessage(&Msg, GetSafeHwnd(), WM_TIMER, 
     WM_TIMER, PM_REMOVE)); 
 
  if(m_bmp.GetSafeHandle() != NULL) 
  { 
   CDC dcMem; 
 
   CClientDC dc(this); 
 
   dcMem.CreateCompatibleDC(&dc); 
 
   CBitmap *pOldBitmap = dcMem.SelectObject(&m_bmp); 
 
   BITMAP bmInfo; 
 
   m_bmp.GetBitmap(&bmInfo); 
 
   dcMem.BitBlt(1, 0, bmInfo.bmWidth - 1, bmInfo.bmHeight, 
     &dcMem, 0, 0, SRCCOPY); 
 
   DrawLineIncColor(&dcMem, 0, bmInfo.bmHeight); 
 
   dcMem.SelectObject(pOldBitmap); 
 
   dcMem.DeleteDC(); 
 
   Invalidate(FALSE); 
  } 
 } 
 
 CStatic::OnTimer(nIDEvent); 
} 

  Пожалуй, это наиболее значимые аспекты реализации данного элемента управления.

Класс окна компонента Splash Screen.

  Теперь займемся созданием окна-заставки.
  Как я уже говорил, само окно Splash Screen будет представлять собой диалог (забегая вперед, скажу, что диалог будет модальным).

  Создадим шаблон диалога окна-заставки (я присвоил ему идентификатор IDD_SPLASH_SCREEN_DIALOG).
В редакторе ресурсов уберем у диалога заголовок, системное меню и границу, выставим расширенный стиль Tool Window.
Добавим в шаблон диалога элемент статический текст (в него будет выводиться информация о состоянии процесса инициализации приложения). Зададим его идентификатор : IDC_SPLASH_STATUS_STATIC.

  Теперь создадим класс диалога на основе только что созданного шаблона (назовем его CSplashWindow).

  Создадим c помощью инструмента ClassWizard в классе CSplashWindow переменную-член класса CString m_strStatusText, связанную через механизм DDX с идентификатором IDC_SPLASH_STATUS_STATIC – так удобнее будет устанавливать текст.

  Хочу обратить ваше внимание на то, что для передачи текста в элемент управления ‘статический текст’ окна Splash Screen из основного потока приложения, придется создать некоторый механизм взаимодействия между потоками (так как окно нашей заставки инициализируется в другом потоке, то неизбежно возникнут проблемы при работе внутренних механизмов MFC - поверьте мне на слово, я это проверял).
Сделать это достаточно просто. Вместо прямых вызовов функций для установки текста в окно элемента управления посредством механизма DDX, мы пошлем окну Splash Screen сообщение, с которым и передадим указатель на строку текста.

В классе CSplashWindow объявим идентификаторы сообщений :

 
 
enum PrivateMesssages 
{ 
 WM_SPLASH_SET_STATUS_TEXT = (WM_USER + 1), 
 WM_SPLASH_NEED_CLOSE_SPLASH = (WM_USER + 2) 
}; 

  Первый идентификатор – как раз идентификатор сообщения для передачи текста в окно Splash Screen, сообщение со вторым идентификатором будет посылаться в окно в том случае, если надо его закрыть, завершив работу компонента.

  Объявим и реализуем метод для установки текста (он будет посылать сообщение) и обработчик сообщения WM_SPLASH_SET_STATUS_TEXT :

 
 
BOOL CSplashWindow::SetStatusText(LPCTSTR lpszText) 
{ 
 HWND hWnd = GetSafeHwnd(); 
 
 if(::IsWindow(hWnd)) 
  return ::SendMessage(hWnd, 
   WM_SPLASH_SET_STATUS_TEXT, 0, (LPARAM) lpszText); 
 
 return FALSE; 
 } 
 
LRESULT CSplashWindow::OnSetStatusText(WPARAM, LPARAM lParam) 
{ 
 if(::IsWindow(GetSafeHwnd())) 
 { 
  m_strStatusText = (LPCTSTR) lParam; 
  return (LRESULT) UpdateData(FALSE); 
 } 
 
 return FALSE; 
} 

  Добавим в шаблон диалога еще два элемента (сверху и снизу). На панели инструментов редактора ресурсов Visual C++ тип данного элемента называется Picture, а представляет он собой все тот же Static. В свойствах этих элементов выставим тип Rectangle, цвет черный (но можно было и белый выставить с тем же успехом).

  Вот так выглядит шаблон диалога в редакторе :


  Через ClassWizard создадим для данных элементов объекты класса CGradientStatic в классе CSplashWindow (m_wndGradientStatic и m_wndGradientStatic2).
  Реализуем две версии конструкторов класса – конструктор по-умолчанию и конструктор с параметрами, задающими цвет фона диалога и цвет отображаемого в диалоге текста :

 
 
CSplashWindow::CSplashWindow() 
CSplashWindow::CSplashWindow(COLORREF clrColorBg, COLORREF clrColorText) 

  Значения цвета фона и текста хранятся в переменных-компонентах класса m_clrBgColor и m_clrTextColor соответственно. Если включен режим изменения цвета фона и текста (вызывался конструктор с параметрами), то в обработчике сообщения WM_INITDIALOG создается кисть цвета, заданного m_clrBgColor, которая впоследствии возвращается из обработчика сообщения WM_CTLCOLOR (в котором также происходит установка цвета текста).

 
 
HBRUSH CSplashWindow::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{ 
 if(m_bEnableColors) 
 { 
  if( (pWnd->GetSafeHwnd() == GetSafeHwnd()) || 
   (pWnd->GetSafeHwnd() == m_wndStatusText.GetSafeHwnd())) 
  { 
   pDC->SetBkMode(TRANSPARENT); 
 
   pDC->SetTextColor(m_clrTextColor); 
 
   return (HBRUSH) *m_pbrBgColor; 
  } 
 } 
 
 return CDialog::OnCtlColor(pDC, pWnd, nCtlColor); 
} 

  Также в обработчике сообщения WM_INITDIALOG мы можем изменить форму окна, назначив ему регион функцией SetWindowRgn. Мы просто “скруглим” края, назначив в качестве региона окна регион созданный функцией CreateRoundRectRgn.

 
 
BOOL CSplashWindow::OnInitDialog() 
{ 
 CDialog::OnInitDialog(); 
 
 if(!m_bEnableColors) 
  m_wndGradientStatic2.ShowWindow(SW_HIDE); 
 
 CRgn rgn; 
 
 CRect rcClient; 
 
 GetClientRect(rcClient); 
  
 rgn.CreateRoundRectRgn( rcClient.left, rcClient.top, rcClient.right, 
    rcClient.bottom, 20, 20); 
 
 SetWindowRgn((HRGN) rgn, TRUE); 
  
 ::SetWindowPos( GetSafeHwnd(), HWND_TOPMOST, 0, 0, 0, 0, 
   SWP_NOMOVE | SWP_NOSIZE); 
 
 m_pbrBgColor = new CBrush; 
  
 m_pbrBgColor->CreateSolidBrush(m_clrBgColor); 
 
 return TRUE; 
} 

  Кроме того будем обрабатывать сообщение WM_NCHITTEST для того, чтобы сделать возможным перемещение окна “мышкой”, “зацепив” окно за область, занятую верхним элементом Static :

 
 
UINT CSplashWindow::OnNcHitTest(CPoint point) 
{ 
 CPoint ptClient(point); 
 
 ScreenToClient(&ptClient); 
 
 CRect rc; 
 
 m_wndGradientStatic.GetWindowRect(&rc); 
 
 ScreenToClient(&rc); 
 
 if(rc.PtInRect(ptClient)) 
  return HTCAPTION; 
 
 return CDialog::OnNcHitTest(point); 
} 

  Закончим создание данного класса тем, что добавим пустые обработчики командных сообщений с идентификаторами IDOK и IDCANCEL для того, чтобы наше окно не закрывалось при нажатии клавиш Ok или Cancel.

Класс потока пользовательского интерфейса компонента Splash Screen.

  И, наконец, создадим класс потока пользовательского интерфейса для нашего компонента. По сути именно этот класс можно назвать классом компонента Splash Screen (он будет содержать методы для инициализации и завершения работы заставки). В нем будет инкапсулирован объект класса CSplashWindow.

  Всего данный класс будет содержать три метода (он будет очень прост в использовании) :

 
 
BOOL CSplashScreen::Start() 
void CSplashScreen::Stop() 
BOOL CSplashScreen::SetStatusText(LPCTSTR lpszText) 

  Соответственно, метод Start будет отвечать за вывод заставки на экран, метод Stop – за прекращение работы заставки, а метод SetStatusText будет просто вызывать метод SetStatusText объекта m_wndSplashWnd класса CSplashWindow, инкапсулированного в CSplashScreen, который как вы помните, устанавливает текст в окно компонента.
  При этом нам надо учесть то, что окно заставки будет создаваться и разрушаться в другом потоке, будет запускаться и останавливаться сам поток. То есть необходимо ввести в классе CSplashScreen некоторый объект синхронизации и синхронизировать доступ к данным потока.
  Для этого объявим константы состояний, в которых может находиться наш компонент, переменную, хранящую эти состояния и объект синхронизации (воспользуемся критической секцией) :

 
 
class CSplashScreen : public CWinThread 
{ 
 
 ... 
 
protected: 
 
 enum ThisThreadState 
 { 
  stateClosed = 0, 
  stateOpening = 1, 
  stateExecuting = 2, 
  stateClosing = 3 
 }; 
 
 BOOL m_dwThreadState; 
 CCriticalSection m_objAccessLock; 
 
 ... 
 
} 

  Не буду приводить здесь реализации методов Start() и Stop() – все достаточно тривиально (в методе Start() поток запускается, метод Stop() останавливает поток, при этом проверяется состояние потока (переменная m_dwThreadState), вследствие чего не происходит ошибок при вызовах этих методов в разной последовательности и сразу одного за другим).

  В функции CSplashScreen::InitInstance() происходит создание модального диалога (вызов метода DoModal() для объекта m_wndSplashWnd).

  Последнее, что надо сказать про этот класс – это то, что у него, как и у класса CSplashWindow существует две версии конструкторов – конструктор по умолчанию и конструктор с параметрами, задающими цвет фона окна Splash Screen и цвет отображаемого в окне текста.

  Теперь осталось только занести компонент в галерею компонентов, что я и сделал. Компонент вы можете скачать на странице сайта рассылки : http://SoftMaker.com.ru/sources/components/splash_window/art.htm

Использование компонента Splash Screen.

  Сделаем то, ради чего все и начиналось. В созданном нами демонстрационном проекте создадим объект класса CSplashScreen (он создается динамически) в функции InitInstance приложения, проинициализируем его, вызвав функцию Start, несколько раз установим текст в окно компонента (останавливая каждый раз главный поток на некоторое время функцией Sleep). Остановка работы заставки происходит через две секунды после появления главного окна приложения.

 
 
BOOL CSplashDemoApp::InitInstance() 
{ 
 m_pSplash = new CSplashScreen(RGB(0x99, 0xcc, 0xff), RGB(64, 64, 64)); 
 
 if(m_pSplash == NULL) 
  return FALSE; 
 
 m_pSplash->Start(); 
 
 m_pSplash->SetStatusText("Инициализация..."); 
 
 Sleep(1500); 
 
 ... 
 
 m_pSplash->SetStatusText("Загрузка модулей..."); 
 
 Sleep(1500); 
 
 ... 
 
 CMainFrame* pFrame = new CMainFrame; 
  
 m_pMainWnd = pFrame; 
 
 pFrame->LoadFrame(IDR_MAINFRAME, 
  WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, 
  NULL); 
 
 m_pSplash->SetStatusText("Запуск..."); 
 
 pFrame->ShowWindow(SW_SHOW); 
 
 BringWindowToForeground(pFrame); 
 
 return TRUE; 
} 

  А вот то, что получилось в итоге (так выглядит окно заставки компонента Splash Screen) :


  Как видно, из InitInstance вызывается функция BringWindowToForeground – это весьма интересная функция. Речь о ней – в следующей статье.

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

  В демонстрационном проекте также хорошо показаны возможности разработанного элемента управления CGradientStatic. Скоро на странице сайта : http://SoftMaker.com.ru/sources/classes/gradient_static/art.htm появится статья, отдельно описывающая этот класс, но уже сейчас исходники этого класса доступны для скачивания вместе с демо-примером, ярко демонстрирующим возможности этого класса, с этой же страницы.

  И, напоследок – я добавил компонент SplashScreen к программе – файловому-менеджеру. Обновленную версию проекта вы можете скачать как обычно на главной странице проекта.
    На сегодня Все.

Автор статьи : Вахтуров Виктор.  

Исходный код проекта, рассматриваемого в статье вы можете найти на сайте рассылки SoftMaker.com.ru на главной странице проекта.

Статья "Перемещение окна на передний план".

  

Преамбула.

  Да, встает иногда проблема...

  Начну издалека.
  Однажды занялся я написанием класса для реализации окна Splash Screen (если кто не знает - это такое окно, выскакивающее перед появлением основного окна приложения в случае если приложение запускается долго - для того, чтобы пользователь мог видеть, что программа все-таки запускается, а не зависла). Писал на VC++ 6.0., MFC.

  Задачу я реализовал достаточно быстро, создав два класса - собственно, класс окна и класс потока, производный от класса CWinThread. Дело в том, что по моему замыслу, в окне должна была крутиться нехитрая анимация, а для этого требовалась обработка сообщений WM_TIMER. Да и компонент получался более качественным (Splash Screen мог висеть хоть час, а за это время в основном потоке приложения происходила работа по инициализации и никаких проблем с перерисовкой Splash Screen не возникало).

  Но вот начались проблемы. Сразу же после начала тестирования выяснилась нехорошая особенность : при закрытии окна Splash Screen происходила активизация окна другого приложения (не моего), что отражалось на панели задач. То есть пользователь видел как появляется окно заставки, под ним появляется главное окно приложения, а затем пропадает окно заставки и главное окно перемещается на задний план, закрываясь окном другого приложения, оставляя только мигающую кнопку на панели задач.

  Сразу же возникла мысль перемещать главное окно программы на передний план, используя функцию CWnd::SetForegroundWindow.

  Но не тут то было !

Амбула.

  Все мои попытки заставить работать функцию SetForegroundWindow как надо, не увенчались успехом (надо сказать, что работала моя программа под управлением Windows2000) - только мигающая кнопка на TaskBar-е.
Чтож, пришлось "копать", и вот что я нарыл.
Да, действительно - в Windows 95/98 функция SetForegroundWindow выдвигала окно на передний план без проблем. А вот в Windows2000 любимая фирма Microsoft изменила ее поведение, сделав так, чтобы только активный процесс (foreground process) мог переместить окно на передний план с ее помошью. Но зато мигающая кнопочка на панели задач должна будет привлечь внимание пользователя... Меня это совсем не устраивало.
Хождение по интернету в поисках идеи реализации возможности активизации своего приложения без проблем и изучение нескольких десятков источников (почти всегда повторяющих друг друга) позволило мне выделить несколько способов решения данной задачи (которые, впрочем, оказались не особо работоспособными и вместо которых, я предложу свой).

Итак.

Способ 1

  Во многих источниках была описана недокументированная функция SwitchToThisWindow, входящая в состав модуля user32.dll. К сожалению, эта функция не входит в библиотеку импорта, поэтому адрес ее точки входа необходимо получать с помощью функции GetProcAddress.

Прототип этой функции такой :

SwitchToThisWindow(HWND hWndSwitchTo, BOOL bMinimize), где
hWndSwitchTo - дескриптор окна, которое надо активизировать, а параметр bMinimize - указывает функции, надо ли (если он TRUE) активизировать окно в минимизированном состоянии.

Пример использования можно привести такой :

 
 
// Пусть hActivateWnd - дескриптор окна, которое надо активизировать. 
 
HMODULE hUser32ModuleHandle = GetModuleHandle("user32.dll"); 
 
if(hUser32ModuleHandle == NULL) 
   ... // Error 
 
// Объявим указатель на функцию.(FARPROC &) 
void (__stdcall *SwitchToThisWindow)(HWND, BOOL); 
 
SwitchToThisWindow = GetProcAddress(hUser32ModuleHandle, "SwitchToThisWindow"); 
SwitchToThisWindow(hActivateWnd, TRUE); 

  Самое обидное то, что в последних версиях Windows поведение этой функции аналогично поведению функции SetForegroundWindow.

Способ 2

  В версиях ОС, начиная с Windows 98 появился некий системный параметр, задающий интервал времени с момента последнего пользовательского ввода, по истечении которого приложение сможет успешно активизировать своё окно с помощью функции SetForegroundWindow. До этого момента в результате применения SetForegroundWindow мы будем получать только мигающую кнопку на панели задач.

  Запросить и установить этот параметр можно с помощью функции SystemParametersInfo, передавая в нее в качестве первого параметра константы SPI_GETFOREGROUNDLOCKTIMEOUT и SPI_SETFOREGROUNDLOCKTIMEOUT соответственно.

  Таким образом можно попытаться установить значение этого параметра в ноль, вызвать функцию SetForegroundWindow, а затем восстановить прежнее значение параметра.

  Это можно реализовать, например, так :

 
 
// Пусть hActivateWnd - дескриптор окна, которое надо активизировать. 
 
DWORD dwUserInputTimeout; 
 
SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &dwUserInputTimeout, 0); 
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, 0); 
SetForegroundWindow(hActivateWnd); 
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (LPVOID) dwUserInputTimeout, 0); 

  Когда я опробовал это метод (под Win2000), то сильно разочаровался - ровно никакого эффекта (и действительно - вспомним, что только активный процесс в Windows 2000 может переместить окно на передний план с помошью SetForegroundWindow). Но я взял на заметку эту идею.Да, кстати, на всякий случай приведу значения констант, описанных выше (если вдруг вам придется их объявлять вручную) :

 
 
#define SPI_GETFOREGROUNDLOCKTIMEOUT        0x2000 
#define SPI_SETFOREGROUNDLOCKTIMEOUT        0x2001 

Способ 3

  Далее...

  SetForegroundWindow работает правильно если вызывающий её процесс является активным (какой либо поток этого процесса обрабатывает пользовательский ввод). И вот тут то пора бы вспомнить о такой функции как AttachThreadInput, с помощью которой можно "подключиться" своим потоком к обработке пользовательского ввода (фактически объединить очереди сообщений двух потоков - чужого, обрабатывающего пользовательский ввод, и нашего). Таким образом можно попробовать на некоторое время "подключиться" к обработке пользовательского ввода, произвести вызов SetForegroundWindow и снова "отключиться".

Реализуем это так :

 
 
// Пусть hActivateWnd - дескриптор окна, которое надо активизировать. 
 
HWND hWndActive = ::GetForegroundWindow(); 
 
DWORD dwThreadID = ::GetCurrentThreadId(); 
DWORD dwCurThreadID = ::GetWindowThreadProcessId(hWndActive, 0); 
 
AttachThreadInput(dwThreadID, dwCurThreadID, TRUE); 
 
SetForegroundWindow(hActivateWnd); 
 
AttachThreadInput(dwThreadID, dwCurThreadID, FALSE); 

  Практически везде утверждается, что данный метод работает и в ОС Windows 98/Me, и в ОС Windows 2000.

  Как бы не так ! У меня не сработал и он.

  Что же делать ?

  Все же есть один более или менее верный метод "выдвинуть" свое окно на передний план. Он очень прост, но с помошью него мы не достигнем цели окончательно.

Способ 4

  Метод заключается в установке позиции окна как topmost помощью функции SetWindowPos (в качестве параметра-дескриптора окна-предшественника в Z-порядке в функцию надо передать константу HWND_TOPMOST), а затем обратного действия - снятия атрибута topmost путем вызова функции SetWindowPos с вышеназванным параметром, равным HWND_NOTOPMOST.

  То есть вот так :

 
 
// Пусть hActivateWnd - дескриптор окна, которое надо активизировать. 
 
::SetWindowPos(hActivateWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 
::SetWindowPos(hActivateWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 

  Вышеописанный метод перемещает окно на передний план, но не активизирует его. К тому же, в системе может быть несколько "самых-верхних" (TOPMOST) окон, и наше окно может и не оказаться на самом верху.

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

  Итак, идея проста : надо установить временной интервал, описанный в Способе 2 в ноль, "поднять" окно над другими, сделав его topmost как в способе 4, "подключаться" к пользовательскому вводу, использовать ::SetForegroundWindow, "отключаться" от обработки пользовательского ввода и проверять, есть ли результат. Процедуру с "подключением", "отключением" и вызовом ::SetForegroundWindow будем повторять до тех пор, пока наше окно все-таки не станет активным. Потом надо не забыть сделать окно не topmost, а временной интервал возвратить в первоначальное значение.

  А вот и сама функция :

 
 
BOOL BringWindowToForeground(CWnd *pWnd) 
{ 
 if(pWnd == NULL) 
  return FALSE; 
 
 if(!::IsWindow(pWnd->GetSafeHwnd())) 
  return FALSE; 
 
 DWORD dwUserInputTimeout; 
 
 ::SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &dwUserInputTimeout, 0); 
 ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, 0); 
 
 ::SetWindowPos(pWnd->GetSafeHwnd(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 
 
 HWND hCurWnd; 
 
 while((hCurWnd = ::GetForegroundWindow()) != pWnd->GetSafeHwnd()) 
 { 
  HWND hWndActive = ::GetForegroundWindow(); 
 
  DWORD dwThreadID = ::GetCurrentThreadId(); 
  DWORD dwCurThreadID = ::GetWindowThreadProcessId(hWndActive, 0); 
 
  AttachThreadInput(dwThreadID, dwCurThreadID, TRUE); 
 
  pWnd->SetForegroundWindow(); 
 
  AttachThreadInput(dwThreadID, dwCurThreadID, FALSE); 
 
  Sleep(20); 
 } 
 
 ::SetWindowPos(pWnd->GetSafeHwnd(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); 
 
 ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (LPVOID) dwUserInputTimeout, 0); 
 
 return TRUE; 
} 

  Быть может, в будущем перестанет работать и этот метод (от Microsoft всего можно ожидать). Будем надеяться, что этого не случиться.

Автор статьи : Вахтуров Виктор.  

Подписчикам

  Дабы заранее разрешить возможные недоразумения, прошу Вас помнить, что Вопросы публикуются в рассылке только один раз. Поэтому, если Вам не ответили в этой рассылке, или ваш вопрос не был опубликован, пришлите его еще раз. Не стоит отвечать на вопрос, который был задан в предыдущей рассылке (за исключением случая, когда он снова опубликован в этой).
Для того, чтобы задать свой вопрос, пришлите письмо по этой ссылке.
Для того, чтобы ответить на вопрос, надо кликнуть по ссылке "ответить", расположенной под текстом вопроса.
Обо всех ошибках, замеченных в данной рассылке, а также своих предложениях и пожеланиях пишите сюда.

Вопросы

  Для того, чтобы задать свой вопрос, кликните этой ссылке (вопрос будет опубликован в следующем номере).
Вы можете задавать любые вопросы, касающиеся программирования на языке C и C++. Это могут быть вопросы касающиеся как конструкций языка, применения библиотек классов, шаблонов (таких как MFC или STL), использования компиляторов, так и самой философии программирования на C или C++. Здесь нет ограничений - спрашивайте и получайте ответы.


Вопрос № 2 ( stringer2002 )

Моё имя Сергей и меня давно беспокоит вопрос: "Почему в среде
разработки Microsoft Visual C++ 6.0, а также в Visual Studio.NET
нет стандартного компонента таблицы?". Много примеров написано по
использованию Microsoft FlexGrid, но мне он не нравиться. Что Вы
можете мне посоветовать?

  Ответить на вопрос


Вопрос № 3 ( Nikolka )

Недавно подписался на Вашу рассылку - очень рад, что есть кому задать
свои вопросы по MS VC++.

Есть обычный проект(с MFC) проект на VC++ 7.0.
В классе Doc есть переменная int m_kolvo;
В классе Dialog1 есть спинер CSpinButtonCtrl m_spin с окошком CEdit m_dat
Надо передать в диалоге данные из m_kolvo в m_dat и при изменении счиитать обратно.

 
void CDoc::OnDialog1() 
{ 
 CDialog1 dlg; 
 ??? 
 if(dlg.DoModal()==IDOK) 
 { 
  ???? 
  SetModifiedFlag(); 
  UpdateAllViews(NULL); 
 } 
} 

Еще вопрос - стратегический. Насчет сохранения и считывания данных и настроек.
Можно в файле, а можно в реестре. Как лучше и каким образом?

С уважением, Николай

  Ответить на вопрос


Вопрос № 4 ( Фёдор Лактиов )

не люблю MFC, пишу всё на API. В мире ИТ уже есть надстройки над API. Хотелось бы узнать о них побольше.

  Ответить на вопрос


Вопрос № 5 ( Евгений )

Подскажите плиз, как правильно, корректно установить шрифт в Windows 98?

  Ответить на вопрос

Ответы

  В данной рассылке нет ответов, так как не было задано вопросов в предыдущей.
Задавайте свои вопросы, и Вы обязятельно получите ответ.

Дискуссионные листы компьютерной тематики


Программирование. Форум !!!

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

Поисковые системы. Форум !!!

Этот дискуссионный лист посвящен обсуждению поиковых систем, методов индексации сайтов поисковиками, способам оптимизации сайта под поисковые системы.

Хостинг. Обзоры и обсуждения платного и бесплатного хостинга.

Вы ищете хостинг (платный, бесплатный) ? Хотите спросить совета в выборе ? Можете обсудить это здесь. Поделитесь советом, если знаете. Или узнайте больше. Все о хостинге.


Краткая история рассылки (итоги).


  Те из подписчиков, которые были подписаны на рассылку с первого номера, наверно помнят, что с самого начала рассылка называлась "C/C++ - вопросы и ответы". И предназначалась именно для обмена опытом в области программирования на C и C++.
Количество подписчиков было достаточно мало, и рассылка не могла развиваться дальше.
Поэтому с номера 3 расылки была начата публикация цикла статей по MFC. Основной упор делался на недокументированные функции и нестандартное использование классов библиотеки.
В номере 3 была опубликована статья "Форматирование строк", рассказывающая про недокументированные функции обработки строк в MFC.
В номере 4 и номере 5 в двух частях статьи "Внутренние ресурсы библиотеки MFC" был дан обзор внутренних ресурсов библиотеки, о которых не упоминается в документации. Номер 6 рассылки во многом поменял ее концепцию. Рассылка была переименована в "Visual C++ - расширенное программирование" и была начата публикация цикла статей, описывающих создание программы-файлового менеджера. В номере 7, номере 8 и номере 9 были описаны нестандартные приемы создания пользовательского интерфейса (был описан процесс совершенствования стандартного сплиттера MFC, процесс разработки собственных панелей инструментов).

  Надеюсь, дорогие подписчики, что эта рассылка будет интересна и полезна для вас.

Всего доброго. До встречи в следующей рассылке.

Любой из материалов, опубликованных в этой рассылке, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное