Элементы управления

Содержание статьи:

Предисловие.
Концепция.
Кнопка.
Список с прокруткой.
Радио кнопка (Radio button).
Check box.
Менеджер элементов управления.

Предисловие.

Вот тот момент, которого мы все ждали (как говорит моя сестра: “настало время Х, час Ч и полная Ж”) – наконец-то можно приступить к созданию элементов управления. В данном уроке я покажу, как можно без лишних хлопот создать такие вещи как кнопки, радио кнопки, чек боксы и списки с прокруткой (для краткости буду называть их кнопками). Приступим?

Концепция.

к началу статьи
Для простоты разработки, организуем все наши классы в виде иерархии на манер MFC. То есть в базовом классе будут объявлены всевозможные методы (наляпаем кучу виртуальных функций, которые будут выполнять наиболее общие действия), а в производных классах они будут (при необходимости) переопределены. При этом в производных классах не будем добавлять никаких новых открытых методов. Это позволит создать код, которому будет безразлично, с каким из объектов иерархии он работает, интерфейс-то у них одинаковый. Итак, вот базовый класс:
class	cCommonControl{
public:

static int CONTROL_ID_DISPETCHER;

int SelectedTextureID;
int ActiveModeTextureID;
int InactiveModeTextureID;

int OnMoveSoundID;
int OnClickSoundID;

int CONTROL_ID;

//Vertex Data
cD3DVertexBuffer<cD3DVertex> VData;

HWND MainWindow;

virtual void OnMouseMove( int ){
SelectedTextureID = ActiveModeTextureID;
}
virtual void OnMouseMoveSound( void ){
if( SelectedTextureID != ActiveModeTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}
virtual void OnMouseOverSound( void ){
if( SelectedTextureID == ActiveModeTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}
virtual void OnMouseOver( int ){
SelectedTextureID = InactiveModeTextureID;
}
virtual void OnRender( LPDIRECT3DDEVICE8 D3DDevice ){
if( SelectedTextureID < 0 ){
VData.Render( D3DDevice , FVF_CD3DVERTEX );
}
else{
TextureManager[ SelectedTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX );
TextureManager[ SelectedTextureID ].ResetTexture( D3DDevice , 0 );
}
}
virtual void OnClick( int wParam = 0 ){
if( OnClickSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnClickSoundID );
}
SendMessage( MainWindow , WM_CONTROL_ONCLICK , wParam , CONTROL_ID );
}
virtual void SetAppearance( int AMTID , int IMTID ){
ActiveModeTextureID = AMTID;
InactiveModeTextureID = IMTID;
}
virtual void SetAppearance( char * , char * );
virtual void SetAAppearance( char * );
virtual void SetIAppearance( char * );
virtual void SetASAppearance( char * ){}
virtual void SetISAppearance( char * ){}
virtual void SetGeometry( float , float ,
float , float , float , float ){};
virtual void SetGeometry( float , float ,
float , float , float ){};
virtual void SetGeometry( float , float ,
float , float ){};
virtual void SetAppearance( char * , char * ,
char * , char * ){}
virtual void AttachToGroup( int ){}
virtual int GetGroup( void ){return( 0 );}
virtual void Reset( void ){}
virtual void AddVar( char * ){}

cCommonControl( void ){
VData.Vertexes.Cursor = 0;
CONTROL_ID = CONTROL_ID_DISPETCHER++;
OnMoveSoundID = OnClickSoundID = -1;
}
virtual ~cCommonControl(){
VData.Release();
}
};
Вот что находится в этом классе:
CONTROL_ID_DISPETCHER – с помощью этого поля мы будем в конструкторах присваивать элементам управления уникальные идентификаторы.
SelectedTextureID – курсор текстуры для рендерринга
ActiveModeTextureID – курсор активной текстуры в TextureManager’e
InactiveModeTextureID – курсор неактивной текстуры в TextureManager’e (две текстуры нужны для эффекта “подсвечивания” кнопок при наведения на них курсора)
OnMoveSoundID – курсор на звук в MediaManager’e, который будет проигрываться при попадании курсора на элемент управления
OnClickSoundID – курсор на звук в MediaManager’e звук, который будет проигрываться при нажатии на кнопки
CONTROL_ID – идентификатор элемента управления.
VData – массив вершин, из которых будут складываться полигоны кнопок
MainWindow – хендл окна
OnMouseMove – метод, вызываемый при наведении курсора на кнопку
OnMouseMoveSound – метод проигрывающий соответствующий звук
OnMouseOver – вызывается, когда пользователь уберёт курсор с кнопки
OnMouseOverSound – опять-таки проигрываем звук
OnRender – рендерринг элемента управления
OnClick – вызывается, когда пользователь нажал на кнопку
SetAppearance, SetAAppearance, SetIAppearance, SetASAppearance, SetISAppearance – установка внешнего вида элементов управления
SetGeometry – заполнение массива VData
AttachToGroup – присоединение радио кнопка к группе
GetGroup – возвращает идентификатор группы к которой принадлежит радио кнопка
Reset – сбрасывает настройки элемента управления в начальное состояние
AddVar – добавление элемента в список с прокруткой
Не пытайтесь сразу разобраться во всех этих функциях и полях, т.к. некоторые из них используются единожды в каком-либо элементе управления. Поэтому, свалив их все в кучу, никакой целостной картины Вы не получите. Просто по мере создания того или иного элемента управления я буду раскрывать роль того или иного метода.

Кнопка.

к началу статьи
Пожалуй, самый простой, понятный и знакомый элемент управления.
class	cButton:public cCommonControl{
public:
cButton( void ):cCommonControl(){}
cButton( float , float , float , float , float );
virtual void SetGeometry( float , float , float , float , float );
};

void cButton::SetGeometry( float h , float l , float x ,
float y , float z ){
VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( l , h , 0 , 0xffffffff , 1 , 0 ) );
VData.AddVertex( cD3DVertex( 0 , h , 0 , 0xffffffff , 0 , 0 ) );

VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( l , 0 , 0 , 0xffffffff , 1 , 1 ) );
VData.AddVertex( cD3DVertex( l , h , 0 , 0xffffffff , 1 , 0 ) );

for( int i( 0 ) ; i < 6 ; i++ )
VData[ i ] += cVector3( x , y , z );
}

//левый нижний угол переносится в точку x , y
cButton::cButton( float h , float l , float x , float y , float z ){
VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( l , h , 0 , 0xffffffff , 1 , 0 ) );
VData.AddVertex( cD3DVertex( 0 , h , 0 , 0xffffffff , 0 , 0 ) );

VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( l , 0 , 0 , 0xffffffff , 1 , 1 ) );
VData.AddVertex( cD3DVertex( l , h , 0 , 0xffffffff , 1 , 0 ) );

for( int i( 0 ) ; i < 6 ; i++ )
VData[ i ] += cVector3( x , y , z );
}
Два конструктора и переопределённый метод SetGeometry, в котором просто создаем два треугольника, образующие кнопку. Все остальные функции взяты из базового класса. Рассмотрим каждую из них:
void	cCommonControl::SetAppearance( char * AMTID , char * IMTID ){
ActiveModeTextureID = TextureManager.GetTextureCursor( AMTID );
InactiveModeTextureID = TextureManager.GetTextureCursor( IMTID );
SelectedTextureID = InactiveModeTextureID;
}
Просто из соответствующего менеджера тянем курсоры текстур, коих всего две. Одна нужна для отображения подсвеченной кнопки (при “наезде” курсора кнопка будет подсвечиваться), вторая для неподсвеченной кнопки (когда курсор находится вне области кнопки). SelectedTextureID – курсор текстуры, с которой будем рендеррить треугольники. Его смысл станет понятен позже.
void	cCommonControl::OnRender( LPDIRECT3DDEVICE8 D3DDevice ){
if( SelectedTextureID < 0 ){
VData.Render( D3DDevice , FVF_CD3DVERTEX );
}
else{
TextureManager[ SelectedTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX );

TextureManager[ SelectedTextureID ].ResetTexture( D3DDevice , 0 );
}
}
Функция рендерринга. Здесь просто проверяем, если текстура есть, то устанавливаем её, если нет, то рисуем только полигоны.
void	cCommonControl::OnMouseMove( int ){
SelectedTextureID = ActiveModeTextureID;
}
Функция, вызываемая при попадании курсора в область кнопки. Я не стал вводить флаг, сигнализирующий состояние кнопки – это потребовало дополнительных условных операторов, бесспорно изуродовавших бы код. Поэтому в функции рендерринга ставим текстуру с курсором SelectedTextureID, а какая это текстура не важно.
void	cCommonControl::OnMouseOver( int ){
SelectedTextureID = InactiveModeTextureID;
}
Делаем кнопку неподсвеченной.
void	cCommonControl::OnMouseMoveSound( void ){
if( SelectedTextureID != ActiveModeTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}

void cCommonControl::OnMouseOverSound( void ){
if( SelectedTextureID == ActiveModeTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}
Начинаем проигрывать соответствующий музыкальный файл, при попадании (уходе) курсора в (из) область кнопки, предварительно проверяя, есть ли вообще какой-либо файл для проигрывания. Первый же if нужен для того, что бы на каждой итерации, при нахождении курсора в области кнопки на протяжении нескольких итераций, не начинал заново проигрываться звуковой эффект.
void	cCommonControl::OnClick( int wParam = 0 ){
if( OnClickSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnClickSoundID );
}
SendMessage( MainWindow , WM_CONTROL_ONCLICK , wParam , CONTROL_ID );
}
Этот метод вызывается, когда пользователь нажимает на кнопку. Тогда запускается соответствующий эффект и приложению посылается сообщение, что такая-то кнопка была нажата.

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

void	cCommonControlManager::RenderByLayer( LPDIRECT3DDEVICE8 D3DDevice , int l ){
for( int i( 0 ) ; i < CommonControlsInfo.Cursor ; i++ )
if( Layers[ i ] == l )
( *this )[ i ]->OnRender( D3DDevice );
}
В качестве параметра передается девайс и идентификатор слоя, который нужно рендеррить. Дело в том, что все элементы управления обязаны принадлежать какому-либо слою, тогда, согласно моей задумке, в зависимости от значения второго параметра, элементы управления будут сортироваться на видимые и невидимые.
void	cCommonControlManager::MouseInput( int layer ){
cVector4 e1,e2,e3,c;
e3 = -Camera.target;
e2 = Camera.norm;
e1 = e2 * e3;
c = Camera.eye;

POINT MousePosition;
GetCursorPos( &MousePosition );

float x( ( float )( - D3D_SCREEN_WIDTH / 2 + MousePosition.x ) /
( D3D_SCREEN_WIDTH / 2 ) );
float y( ( float )( D3D_SCREEN_HEIGHT / 2 - MousePosition.y ) /
( D3D_SCREEN_HEIGHT / 2 ) );
float z( 0 );

float s( Camera.eye.Norm() * tan( D3DX_PI / 8 ) );
float x_s( ( float )D3D_SCREEN_WIDTH / D3D_SCREEN_HEIGHT );

for( int i( 0 ) ; i < Layers.Cursor ; i++ ){
if( Layers[ i ] == layer ){
for( int j( 0 ) ;
j < CommonControls[ i ].GetPtr()->VData.Vertexes.Cursor / 3 ; j++ )
{
if( TriangleBelong( cPolygon4(
CommonControls[ i ].GetPtr()->VData[ 3 * j + 0 ] ,
CommonControls[ i ].GetPtr()->VData[ 3 * j + 1 ] ,
CommonControls[ i ].GetPtr()->VData[ 3 * j + 2 ] ) ,
s * y * e2 + s * x_s * x * e1 ) )
{
CommonControls[ i ].GetPtr()->OnMouseMoveSound();
CommonControls[ i ].GetPtr()->OnMouseMove( j );
goto end;
}
}
CommonControls[ i ].GetPtr()->OnMouseOverSound();
CommonControls[ i ].GetPtr()->OnMouseOver( j );
end:;
}
}
}
Это одна из самых главных функций в этом уроке. С её помощью определяется положение мыши и элемент управления, в области которого находится курсор мыши. Если он найден, то этот элемент становится подсвеченным и начинает проигрываться соответствующий этому событию звуковой файл.

В этой функции выполняются следующие действия:
1) сначала находим локальную систему координат камеры – e1,e2,e3. Предполагается, что при отрисовке элементов управления точка визирования камеры (eye) находится на оси Z, направление взгляда противоположно этой оси, а нормаль совпадает с осью Y.
2) потом находим координаты курсора (в пикселях).
3) в конце концов, мы находим координаты (x,y,z) курсора в глобальных координатах (как будто курсор перемещается в плоскости XY). Но изначально координаты x и y не пригодны для использования, т.к. они неоднородные (положение курсора изменяется от -1 до 1 по осям x и y), соответственно множителем x_s мы делаем их однородными.
4) а дальше просто все треугольники всех элементов управления записываем в один массив и проверяем принадлежность курсора какому-либо из этих треугольников. При необходимости подсвечиваем (или снимаем подсветку) кнопки.

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

Также хочу подробнее остановиться на функции TtriangleBelong – она возвращает true, если точка, принадлежащая плоскости треугольника, лежит внутри него.

bool TriangleBelong( cPolygon4 p , cVector4 test ){
cSurface4 s_base( p );
cSurface4 s_dir1( p.v1 , p.v2 + s_base.Norm , p.v2 );
if( s_dir1.Upper( test ) ){
cSurface4 s_dir2( p.v2 , p.v3 + s_base.Norm , p.v3 );
if( s_dir2.Upper( test ) ){
cSurface4 s_dir3( p.v3 , p.v1 + s_base.Norm , p.v1 );
if( s_dir3.Upper( test ) )
return( true );
}
}
return( false );
}


Смысл алгоритма в том, что мы дублируем вершины треугольника и сдвигаем их в направлении нормали. Затем через боковые грани получившейся призмы проводим плоскости, так что бы их нормали смотрели внутрь. Если тестируемая вершина лежит выше каждой из трёх плоскостей, то она однозначно лежит внутри этой призмы. Если вершина лежит в плоскости треугольника, то данная функция возвратит true, если вершина лежит внутри треугольника.

Ещё есть функция, обрабатывающая нажатие левой кнопки мыши, но она практически идентична предыдущей функции:

void	cCommonControlManager::LButtonClickHandle( int layer ){
cVector4 e1,e2,e3,c;
e3 = -Camera.target;
e2 = Camera.norm - Proj( Camera.norm , Camera.target );
e2.Normize();
e1 = e2 * e3;
c = Camera.eye;

POINT MousePosition;
GetCursorPos( &MousePosition );

float x( ( float )( - D3D_SCREEN_WIDTH / 2 + MousePosition.x ) /
( D3D_SCREEN_WIDTH / 2 ) );
float y( ( float )( D3D_SCREEN_HEIGHT / 2 - MousePosition.y ) /
( D3D_SCREEN_HEIGHT / 2 ) );
float z( 0 );

float s( Camera.eye.Norm() * tan( D3DX_PI / 8 ) );
float x_s( ( float )D3D_SCREEN_WIDTH / D3D_SCREEN_HEIGHT );

for( int i( 0 ) ; i < Layers.Cursor ; i++ ){
if( Layers[ i ] == layer ){
for( int j( 0 ) ;
j < CommonControls[ i ].GetPtr()->VData.Vertexes.Cursor / 3 ; j++ )
{
if( TriangleBelong( cPolygon4(
CommonControls[ i ].GetPtr()->VData[ 3 * j + 0 ] ,
CommonControls[ i ].GetPtr()->VData[ 3 * j + 1 ] ,
CommonControls[ i ].GetPtr()->VData[ 3 * j + 2 ] ) ,
s * y * e2 + s * x_s * x * e1 ) )
{
CommonControls[ i ].GetPtr()->OnClick( j );
return;
}
}
}
}
}
В функцию OnClick передается номер полигона, на который наведен курсор.

Список с прокруткой.

к началу статьи
class	cSpinControl:public cCommonControl{
cD3DVertexBuffer MainVData;

int NumVars;

int CurrVar;

int ArrowMove;

cArray<int> VarTextures;
public:
cSpinControl( void ){
cCommonControl();
NumVars = CurrVar = 0;
ArrowMove = -1;
VarTextures.Cursor = 0;
}
virtual void SetGeometry( float , float ,
float , float , float , float );
virtual void OnRender( LPDIRECT3DDEVICE8 );
virtual void OnMouseMove( int );
virtual void OnMouseOver( int );
virtual void SetAppearance( char *, char * );
virtual void OnMouseMoveSound( void );
virtual void OnClick( int );
virtual void AddVar( char * );
};
Этот элемент управления состоит из двух частей – кнопки прокрутки (по 2 полигона на каждую, на рисунке помечены зеленым) и поле для вывода выбранного элемента списка (ещё 2 полигона, на рисунке помечено красным).

MainVData – полигоны, на которые будет натягиваться текстура с выбранным вариантом
NumVars – количество элементов в списке
CurrVar – текущий элемент
ArrowMove – если 0, то курсор находится над левой кнопкой, если 1, то курсор находится над правой кнопкой
VarTextures – массив курсоров на текстуры вариантов.
VData – вершины кнопок прокрутки.

Вроде все просто:

void	cSpinControl::SetGeometry( float w1 , float w2 , 
float h , float x , float y , float z ){
//левая кнопка
VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( w1 , h , 0 , 0xffffff , 0.5 , 0 ) );
VData.AddVertex( cD3DVertex( 0 , h , 0 , 0xffffff , 0 , 0 ) );

VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( w1 , 0 , 0 , 0xffffff , 0.5 , 1 ) );
VData.AddVertex( cD3DVertex( w1 , h , 0 , 0xffffff , 0.5 , 0 ) );

//правая кнопка
VData.AddVertex( cD3DVertex( w2-w1 , 0 , 0 , 0xffffff , 0.5 , 1 ) );
VData.AddVertex( cD3DVertex( w2 , h , 0 , 0xffffff , 1 , 0 ) );
VData.AddVertex( cD3DVertex( w2-w1 , h , 0 , 0xffffff , 0.5 , 0 ) );

VData.AddVertex( cD3DVertex( w2-w1 , 0 , 0 , 0xffffff , 0.5 , 1 ) );
VData.AddVertex( cD3DVertex( w2 , 0 , 0 , 0xffffff , 1 , 1 ) );
VData.AddVertex( cD3DVertex( w2 , h , 0 , 0xffffff , 1 , 0 ) );

for( int i( 0 ) ; i < VData.Vertexes.Cursor ; i++ )
VData[ i ] += cVector3( x , y , z );

//полигоны, на которые будем выводить основную инфу
MainVData.AddVertex( cD3DVertex( w1 , 0 , 0 , 0xffffff , 0 , 1 ) );
MainVData.AddVertex( cD3DVertex( w2-w1 , h , 0 , 0xffffff , 1 , 0 ) );
MainVData.AddVertex( cD3DVertex( w1 , h , 0 , 0xffffff , 0 , 0 ) );

MainVData.AddVertex( cD3DVertex( w1 , 0 , 0 , 0xffffff , 0 , 1 ) );
MainVData.AddVertex( cD3DVertex( w2-w1 , 0 , 0 , 0xffffff , 1 , 1 ) );
MainVData.AddVertex( cD3DVertex( w2-w1 , h , 0 , 0xffffff , 1 , 0 ) );

for( int i( 0 ) ; i < VData.Vertexes.Cursor ; i++ )
MainVData[ i ] += cVector3( x , y , z );
}
Param – номер полигона над которым находится курсор мыши (номеруются естественно с нуля). Инициализируем должным образом ArrowMove и текстуры.
void	cSpinControl::OnMouseMove( int Param ){
ArrowMove = Param / 2;
SelectedTextureID = ActiveModeTextureID;
}

void cSpinControl::OnMouseOver( int Param ){
ArrowMove = -1;
SelectedTextureID = InactiveModeTextureID;
}
Добавляем текстуру для нового элемента списка.
void	cSpinControl::AddVar( char *VAR ){
VarTextures.AddEnd( TextureManager.GetTextureCursor( VAR ) );
NumVars++;
}
Рендерринг довольно простой – смотрим, какая кнопка подсвечена и подсвечена ли вообще, а затем рисуем полигоны.
void	cSpinControl::OnRender( LPDIRECT3DDEVICE8 D3DDevice ){

if( ArrowMove == -1)
//ничего не подсвечено
cCommonControl::OnRender( D3DDevice );
else{
if( ArrowMove ){
//правая кнопка подсвечена
TextureManager[ ActiveModeTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX , 6 , 2 );
TextureManager[ ActiveModeTextureID ].ResetTexture( D3DDevice , 0 );
TextureManager[ InactiveModeTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX , 0 , 2 );
TextureManager[ InactiveModeTextureID ].ResetTexture( D3DDevice , 0 );
}
else{
//левая кнопка подсвечена
TextureManager[ ActiveModeTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX , 0 , 2 );
TextureManager[ ActiveModeTextureID ].ResetTexture( D3DDevice , 0 );

TextureManager[ InactiveModeTextureID ].SetTexture( D3DDevice , 0 );
VData.Render( D3DDevice , FVF_CD3DVERTEX , 6 , 2 );
TextureManager[ InactiveModeTextureID ].ResetTexture( D3DDevice , 0 );
}
}
if( VarTextures.Cursor ){
//рисуем выбранный элемент списка
TextureManager[ VarTextures[ CurrVar ] ].SetTexture( D3DDevice , 0 );
MainVData.Render( D3DDevice , FVF_CD3DVERTEX );
TextureManager[ VarTextures[ CurrVar ] ].ResetTexture( D3DDevice , 0 );
}
}

Радио кнопка (Radio button).

к началу статьи
class	cRadioButton:public cCommonControl{
public:
//группа к которой принадлежит
int RB_GROUP;

bool Selected;

int ActiveModeSelTextureID;
int InactiveModeSelTextureID;
cRadioButton( void ):cCommonControl(){
RB_GROUP = 0;
ActiveModeSelTextureID=-1;
InactiveModeSelTextureID=-1;
Selected=false;
}
cRadioButton( float , float , float , float );
virtual void SetGeometry( float , float , float , float );
virtual void SetASAppearance( char * );
virtual void SetISAppearance( char * );
virtual void SetAppearance( char * , char * , char * , char * );
virtual void OnClick( int );
virtual void OnMouseMove( int );
virtual void OnMouseOver( int );
virtual void OnMouseOverSound( void );
virtual void OnMouseMoveSound( void );
virtual void AttachToGroup( int i ){RB_GROUP = i;}
virtual int GetGroup( void ){ return( RB_GROUP ); }
virtual void Reset( void );
}
RB_GROUP – все радио кнопки принадлежат какой-либо группе, эта принадлежность определяется данным полем
Selected – отмечена или нет данная радио кнопка
ActiveModeSelTextureID – текстура подсвеченной, установленной кнопки
InactiveModeSelTextureID – текстура неподсвеченной, установленной кнопки
VData – полигоны элемента управления

Кнопка квадратная… больше мне добавить нечего:

void	cRadioButton::SetGeometry( float w , float x , float y , float z ){
RB_GROUP = 0;

VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( w , w , 0 , 0xffffffff , 1 , 0 ) );
VData.AddVertex( cD3DVertex( 0 , w , 0 , 0xffffffff , 0 , 0 ) );

VData.AddVertex( cD3DVertex( 0 , 0 , 0 , 0xffffffff , 0 , 1 ) );
VData.AddVertex( cD3DVertex( w , 0 , 0 , 0xffffffff , 1 , 1 ) );
VData.AddVertex( cD3DVertex( w , w , 0 , 0xffffffff , 1 , 0 ) );

for( int i( 0 ) ; i < 6 ; i++ )
VData[ i ] += cVector3( x , y , z );
}
В зависимости от значения поля Selected ставим необходимые текстуры
void	cRadioButton::OnMouseOver( int ){
if( Selected ){
SelectedTextureID = InactiveModeSelTextureID;
}
else{
SelectedTextureID = InactiveModeTextureID;
}
}

void cRadioButton::OnMouseMove( int ){
if( Selected ){
SelectedTextureID = ActiveModeSelTextureID;
}
else{
SelectedTextureID = ActiveModeTextureID;
}
}
Опять же в зависимости от Selected либо запускаем стандартный обработчик, либо свой:
void	cRadioButton::OnMouseMoveSound( void ){
if( Selected ){
if( SelectedTextureID != ActiveModeSelTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}
else{
cCommonControl::OnMouseMoveSound();
}
}

void cRadioButton::OnMouseOverSound( void ){
if( Selected ){
if( SelectedTextureID == ActiveModeSelTextureID ){
if( OnMoveSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnMoveSoundID );
}
}
}
else{
cCommonControl::OnMouseOverSound();
}
}
В обработчике клика устанавливаются соответствующие поля и проигрывается музыкальный файл. В конце мы посылаем сообщение менеджеру элементов управления, что бы тот сбросил радио кнопку этой группы, которая была установлена ранее.
void	cRadioButton::OnClick( int wParam = 0 ){
Selected = true;
SelectedTextureID = ActiveModeSelTextureID;
if( OnClickSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnClickSoundID );
}
SendMessage( MainWindow , WM_CONTROL_RB_ONCLICK , CONTROL_ID , NULL );
}

Check box.

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

В GetGroup возвращаем такое значение, что бы случайно не получилось, что какой-то чек бокс принадлежит группе радио кнопок.

class	cCheckBox:public cRadioButton{
public:
virtual void OnClick( int );
virtual int GetGroup( void ){return( -1000000 );}
};

void cCheckBox::OnClick( int ){
Selected = !Selected;

if( Selected ) SelectedTextureID = ActiveModeSelTextureID;
else SelectedTextureID = ActiveModeTextureID;

if( OnClickSoundID >= 0 ){
MediaManager.RunAppropriateMedia( OnClickSoundID );
}
}

Менеджер элементов управления.

к началу статьи
//обертка для cCommonControl *ptr
class cCommonControlPtr{
cCommonControl *ptr;
public:
cCommonControl *GetPtr( void ){return( ptr );}
cCommonControlPtr( void ){ptr=NULL;}
~cCommonControlPtr(){}
void Release( void ){delete [] ptr;}
template<class Type> cCommonControlPtr( Type * p ){ptr = p;}
template<class Type>void operator=( Type *p ){ptr = p;}
};

struct sCommonControlInfo{char Info[256];};

#define CC_BUTTON 0
#define CC_RADIOBUTTON 1
#define CC_SPINCONTROL 2
#define CC_CHECKBOX 3

class cCommonControlManager{
public:
//CommonControlLayers[ i ] – номер слоя, которому принадлежит
//i – й элемент управления
cArray<int> CommonControlLayers;
//названия элементов управления
cArray<sCommonControlInfo> CommonControlsInfo;
//указатели на элементы управления
cArray<cCommonControlPtr> CommonControls;

cCommonControlManager( void ){
CommonControls.Cursor = 0;
CommonControlsInfo.Cursor = 0;
}
void AddControlInfo( char * , int , HWND );
cCommonControl *operator[]( char * );
cCommonControl *operator[]( int );
int GetControlCursor( char * );
void CreateControls( HWND );
void RenderByLayer( LPDIRECT3DDEVICE8 , int );

void MouseInput( int );
void LButtonClickHandle( int );

void HandleControlMessages( unsigned int , int , long );
void UpdateGroup( int );

~cCommonControlManager();
};
Структура менеджера и некоторые его функции покажутся Вам знакомыми, все это мы уже проделывали для текстурного менеджера. Поэтому рассмотрим только новые функции:

Обработчик событий, посылаемых элементами управления.

void	cCommonControlManager::HandleControlMessages( unsigned int msg , 
int wParam , long lParam ){
switch( msg ){
case( WM_CONTROL_ONCLICK ):

if(lParam==CommonControls[GetControlCursor("OK_BUTTON")].GetPtr()->CONTROL_ID)
exit(0);

break;
case( WM_CONTROL_RB_ONCLICK ):

UpdateGroup( wParam );

break;
}
}
WM_CONTROL_ONCLICK – сообщение кнопок
WM_CONTROL_RB_ONCLICK – сообщение радио кнопок (здесь обязательно должна вызываться функция UpdateGroup)

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

void	cCommonControlManager::UpdateGroup( int CONTROL_ID ){
int GROUP_N;
for( int i( 0 ) ; i < CommonControls.Cursor ; i++ )
if( CommonControls[ i ].GetPtr()->CONTROL_ID == CONTROL_ID )
GROUP_N = CommonControls[ i ].GetPtr()->GetGroup();
for( int i( 0 ) ; i < CommonControls.Cursor ; i++ ){
if( CommonControls[ i ].GetPtr()->GetGroup() == GROUP_N &&
CommonControls[ i ].GetPtr()->CONTROL_ID != CONTROL_ID ){
CommonControls[ i ].GetPtr()->Reset();
}
}
}
Следующая функция просто создает пустой элемент управления (и сохраняет его название), не инициализируя ни одного из его полей.
void	cCommonControlManager::AddControlInfo( char *info , 
int CONTROL , HWND hWnd ){
sCommonControlInfo Info;
memcpy( &Info , info , strlen( info )+1 );
CommonControlsInfo.AddEnd( Info );
switch( CONTROL ){
case( CC_BUTTON ):

CommonControls[ CommonControlsInfo.Cursor - 1 ] = new cButton;
CommonControls[ CommonControlsInfo.Cursor - 1 ].GetPtr()->MainWindow = hWnd;
CommonControls.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers[ CommonControlLayers.Cursor - 1 ] = 0;

break;
case( CC_RADIOBUTTON ):

CommonControls[ CommonControlsInfo.Cursor - 1 ] = new cRadioButton;
CommonControls[ CommonControlsInfo.Cursor - 1 ].GetPtr()->MainWindow = hWnd;
CommonControls.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers[ CommonControlLayers.Cursor - 1 ] = 0;

break;
case( CC_SPINCONTROL ):

CommonControls[ CommonControlsInfo.Cursor - 1 ] = new cSpinControl;
CommonControls[ CommonControlsInfo.Cursor - 1 ].GetPtr()->MainWindow = hWnd;
CommonControls.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers[ CommonControlLayers.Cursor - 1 ] = 0;

break;
case( CC_CHECKBOX ):

CommonControls[ CommonControlsInfo.Cursor - 1 ] = new cCheckBox;
CommonControls[ CommonControlsInfo.Cursor - 1 ].GetPtr()->MainWindow = hWnd;
CommonControls.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers.Cursor = CommonControlsInfo.Cursor;
CommonControlLayers[ CommonControlLayers.Cursor - 1 ] = 0;

break;
default:

MessageBox( 0 , "Неопознанный элемент управления." , "Ошибка." , 0 );
exit( 0 );

break;
}
}
Вот и все… Даже не верится, что мы закончили! Оглядываясь назад, понимаешь, что очень многое было сделано – мы создали большую часть вашего будущего движка, немного поняли, как он будет выглядеть, разобрались с довольно большим пластом рутины. Но предстоит сделать ещё больше. Я пока не решил, за что возьмусь в следующий раз, поэтому следите за новостями. До связи!

к началу статьи

ЗЫ: способ связи прежний – dodonov_a_a (___AT) inbox.ru


Смежные вопросы:
Урок 1. Инициализация. Вершинные буфферы и камера.
Урок 2. Проигрывание медиа-файлов.
Урок 3. Автоматизация загрузки и управления аудио и видео ресурсами.
Урок 4. Текстурирование. Текстурный менеджер.
Урок 5. Шрифты в Direct3D.

Статьи с других сайтов:
Философия программирования: Интерфейс пользователя.


Исходные коды.
© 2004-2005 Savardge.ru
Hosted by uCoz