Tutorial: Hospedar contenido de WPF en Win32
Windows Presentation Foundation (WPF) proporciona un entorno enriquecido para crear aplicaciones. Sin embargo, si se tiene una inversión sustancial en código Win32, quizá sea más eficaz agregar funcionalidad de WPF a la aplicación en lugar de reescribir el código original. WPF proporciona un mecanismo sencillo para hospedar contenido de WPF en una ventana de Win32.
En este tutorial se describe cómo escribir una aplicación de ejemplo de hospedaje de contenido WPF en una ventana de Win32, que hospeda contenido WPF en una ventana de Win32. Puede extender este ejemplo para hospedar cualquier ventana de Win32. Dado que implica combinar código administrado y no administrado, la aplicación se escribe en C++/CLI.
Requisitos
En este tutorial se da por supuesto un conocimiento básico de la programación en WPF y Win32. Para obtener una introducción básica a la programación de WPF, vea Introducción. Para obtener una introducción a la programación de Win32, consulte cualquiera de los numerosos libros publicados sobre el tema, en particular Programming Windows, de Charles Petzold.
Dado que el ejemplo que acompaña a este tutorial se implementa en C++/CLI, el tutorial supone que está familiarizado con el uso de C++ para programar la API de Windows, y que además conoce la programación de código administrado. Tener un conocimiento de C++/CLI es útil, pero no esencial.
Nota:
En el tutorial se incluye una serie de ejemplos de código del ejemplo asociado. Sin embargo, para una mejor lectura, no se incluye el código de ejemplo completo. Puede ver el código de ejemplo completo en el ejemplo de hospedaje de contenido WPF en una ventana de Win32.
Procedimiento básico
En esta sección se describe el procedimiento básico que se usa para hospedar contenido de WPF en una ventana de Win32. Las secciones restantes explican los detalles de cada paso.
La clave para hospedar contenido WPF en una ventana de Win32 es la clase HwndSource. Esta clase ajusta el contenido de WPF en una ventana Win32, lo que permite incorporarlo a la interfaz de usuario (UI) como una ventana secundaria. En el siguiente enfoque se combina Win32 y WPF en una única aplicación.
Implemente su contenido WPF como una clase administrada.
Implemente una aplicación Windows con C++/CLI. Si está empezando con una aplicación existente y código C++ no administrado, lo normal es que lo habilite para llamar al código administrado; para ello, cambie la configuración del proyecto para incluir la marca de compilador
/clr
.Establezca el modelo de subprocesos en contenedor uniproceso (STA).
Controle la notificación WM_CREATE en el procedimiento de ventana y haga lo siguiente:
Cree un nuevo objeto HwndSource con la ventana primaria como su
parent
parámetro.Cree una instancia de su clase de contenido WPF.
Asigne una referencia al objeto de contenido WPF para la propiedad RootVisual de HwndSource.
Obtenga el HWND para el contenido. La propiedad Handle del objeto HwndSource contiene el identificador de ventana (HWND). Para obtener un HWND que se pueda usar en la parte de la aplicación no administrada, convierta
Handle.ToPointer()
en un HWND.
Implemente una clase administrada que contenga un campo estático para contener una referencia al contenido WPF. Esta clase le permite obtener una referencia al contenido WPF desde su código Win32.
Asigne el contenido WPF al campo estático.
Adjunte un identificador a uno o varios de los eventos de WPF para recibir notificaciones del contenido de WPF.
Comuníquese con el contenido WPF mediante la referencia que almacenó en el campo estático para establecer propiedades, etc.
Nota:
También puede usar contenido WPF. Sin embargo, tendrá que compilarlo por separado como una biblioteca de vínculos dinámicos y hacer referencia a esta biblioteca de vínculos dinámicos desde su aplicación Win32. El resto del procedimiento es similar al descrito anteriormente.
Implementación de la aplicación host
En esta sección se describe cómo hospedar contenido de WPF en una aplicación de Win32 básica. El propio contenido se implementa en C++/CLI como una clase administrada. En su mayor parte, se trata de programación en WPF sencilla. Los aspectos clave de la implementación del contenido se tratan en el apartado Implementación de contenido WPF.
Aplicación básica
El punto de partida para la aplicación host fue crear una plantilla de Visual Studio 2005.
Abra Visual Studio 2005 y seleccione Nuevo proyecto en el menú Archivo.
Seleccione Win32 en la lista de tipos de proyecto de Visual C++. Si el lenguaje predeterminado no es C++, encontrará estos tipos de proyecto en Otros lenguajes.
Seleccione una plantilla de Proyecto Win32, asigne un nombre al proyecto y haga clic en Aceptar para iniciar el Asistente para aplicaciones Win32.
Acepte la configuración predeterminada del asistente y haga clic en Finalizar para iniciar el proyecto.
La plantilla crea una aplicación Win32 básica, que incluye:
Un punto de entrada para la aplicación.
Una ventana con un procedimiento de ventana asociado (WndProc).
Un menú con los encabezados Archivo y Ayuda. El menú Archivo tiene un elemento Salir que cierra la aplicación. El menú Ayuda tiene un elemento Acerca de que inicia un cuadro de diálogo simple.
Antes de empezar a escribir código para hospedar el contenido de WPF, realice dos modificaciones en la plantilla básica.
La primera consiste en compilar el proyecto como código administrado. De forma predeterminada, el proyecto se compila como código no administrado. Sin embargo, dado que WPF se implementa en código administrado, el proyecto debe estar compilado consecuentemente.
En el Explorador de soluciones, haga clic con el botón derecho en el nombre del proyecto y seleccione Propiedades en el menú contextual para iniciar el cuadro de diálogo Páginas de propiedades.
Seleccione Propiedades de configuración en la vista de árbol del panel izquierdo.
Seleccione Compatible con Common Language Runtime en la lista Valores predeterminados del proyecto del panel derecho.
Seleccione Compatible con Common Language Runtime (/clr) en el cuadro de lista desplegable.
Nota:
Esta marca de compilador permite usar código administrado en la aplicación, pero el código no administrado seguirá compilándose como antes.
WPF usa el modelo de subprocesos de contenedor uniproceso (STA). Para que funcione correctamente con el código de contenido de WPF, debe establecer el modelo de subprocesos de la aplicación en STA; para ello, aplique un atributo al punto de entrada.
[System::STAThreadAttribute] //Needs to be an STA thread to play nicely with WPF
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
Hospedaje de contenido WPF
El contenido de WPF es una aplicación de entrada de dirección simple. Consta de varios controles TextBox para tomar el nombre de usuario, la dirección, etc. También hay dos controles Button, Aceptar y Cancelar. Cuando el usuario hace clic en Aceptar, el controlador de eventos Click del botón recopila los datos de los controles TextBox, los asigna a las propiedades correspondientes y genera un evento personalizado, OnButtonClicked
. Cuando el usuario hace clic en Cancelar, el controlador simplemente genera un OnButtonClicked
. El objeto de argumento de evento para OnButtonClicked
contiene un campo booleano que indica en qué botón se hizo clic.
El código para hospedar el contenido de WPF se implementa en un controlador para la notificación WM_CREATE en la ventana del host.
case WM_CREATE :
GetClientRect(hWnd, &rect);
wpfHwnd = GetHwnd(hWnd, rect.right-375, 0, 375, 250);
CreateDataDisplay(hWnd, 275, rect.right-375, 375);
CreateRadioButtons(hWnd);
break;
El método GetHwnd
toma la información de tamaño y posición más el elemento primario identificador de ventana y devuelve el identificador de ventana del contenido de WPF hospedado.
Nota:
No se puede usar una directiva #using
para el espacio de nombres System::Windows::Interop
. Si lo hace, crearía un conflicto de nombres entre la estructura MSG en ese espacio de nombres y la estructura MSG declarada en winuser.h. En su lugar, debe usar nombres completos para tener acceso al contenido de ese espacio de nombres.
HWND GetHwnd(HWND parent, int x, int y, int width, int height)
{
System::Windows::Interop::HwndSourceParameters^ sourceParams = gcnew System::Windows::Interop::HwndSourceParameters(
"hi" // NAME
);
sourceParams->PositionX = x;
sourceParams->PositionY = y;
sourceParams->Height = height;
sourceParams->Width = width;
sourceParams->ParentWindow = IntPtr(parent);
sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD; // style
System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(*sourceParams);
WPFPage ^myPage = gcnew WPFPage(width, height);
//Assign a reference to the WPF page and a set of UI properties to a set of static properties in a class
//that is designed for that purpose.
WPFPageHost::hostedPage = myPage;
WPFPageHost::initBackBrush = myPage->Background;
WPFPageHost::initFontFamily = myPage->DefaultFontFamily;
WPFPageHost::initFontSize = myPage->DefaultFontSize;
WPFPageHost::initFontStyle = myPage->DefaultFontStyle;
WPFPageHost::initFontWeight = myPage->DefaultFontWeight;
WPFPageHost::initForeBrush = myPage->DefaultForeBrush;
myPage->OnButtonClicked += gcnew WPFPage::ButtonClickHandler(WPFButtonClicked);
source->RootVisual = myPage;
return (HWND) source->Handle.ToPointer();
}
No se puede hospedar el contenido de WPF directamente en la ventana de la aplicación. En su lugar, cree un objeto HwndSource para encapsular el contenido WPF. Este objeto es básicamente una ventana diseñada para hospedar un contenido de WPF. Para que el objeto HwndSource se hospede en la ventana primaria, hay que crearlo como elemento secundario de una ventana de Win32 que forme parte de la aplicación. Los parámetros de constructor HwndSource contienen casi la misma información que se pasaría a CreateWindow al crear una ventana secundaria de Win32.
Luego, cree una instancia del objeto de contenido de WPF. En este caso, el contenido WPF se implementa como una clase independiente, WPFPage
, mediante C++/CLI. También puede implementar el contenido de WPF con XAML. Sin embargo, para ello, debe configurar un proyecto independiente y compilar el contenido de WPF como un archivo DLL. Puede agregar una referencia a ese DLL en su proyecto y usar esa referencia para crear una instancia del contenido WPF.
Para mostrar el contenido de WPF en la ventana secundaria, asigne una referencia al contenido de WPF en la propiedad RootVisual de la clase HwndSource.
La siguiente línea de código adjunta un controlador de eventos, WPFButtonClicked
, al evento OnButtonClicked
del contenido WPF. Este controlador se llama cuando el usuario hace clic en el botón Aceptar o Cancelar. Consulte la sección Comunicación con el contenido WPF para obtener más información sobre este controlador de eventos.
La línea final del código mostrado devuelve el identificador de ventana (HWND) que está asociado al objeto HwndSource. Puede usar este identificador desde su código de Win32 para enviar mensajes a la ventana hospedada, aunque el ejemplo no lo hace. El objeto HwndSource genera un evento cada vez que recibe un mensaje. Para procesar los mensajes, llame al método AddHook para adjuntar un controlador de mensajes y, a continuación, procese los mensajes en ese controlador.
Referencia al contenido WPF
En muchas aplicaciones, deseará comunicarse con el contenido WPF más adelante. Por ejemplo, quizá desee modificar las propiedades del contenido WPF, o quizá quiera que el objeto HwndSource hospede contenido WPF diferente. Para ello, necesita una referencia al objeto HwndSource o al contenido WPF. El objeto HwndSource y su contenido WPF asociado permanecen en memoria hasta que se destruya el identificador de ventana. Sin embargo, la variable que asigne al objeto HwndSource saldrá del ámbito tan pronto se devuelva un resultado desde el procedimiento de ventana. La forma habitual de afrontar este problema con aplicaciones Win32 consiste en usar una variable global o estática. Por desgracia, no es posible asignar un objeto administrado a esos tipos de variables. Puede asignar el identificador de ventana asociado al objeto HwndSource a una variable global o estática, pero eso no proporciona acceso al objeto mismo.
La solución más sencilla para este problema es implementar una clase administrada con un conjunto de campos estáticos que contengan referencias a los objetos administrados a los que es necesario tener acceso. En el ejemplo se usa la clase WPFPageHost
para incluir una referencia al contenido WPF, además de los valores iniciales de una serie de sus propiedades que el usuario puede cambiar posteriormente. Esto se define en el encabezado.
public ref class WPFPageHost
{
public:
WPFPageHost();
static WPFPage^ hostedPage;
//initial property settings
static System::Windows::Media::Brush^ initBackBrush;
static System::Windows::Media::Brush^ initForeBrush;
static System::Windows::Media::FontFamily^ initFontFamily;
static System::Windows::FontStyle initFontStyle;
static System::Windows::FontWeight initFontWeight;
static double initFontSize;
};
La última parte de la función GetHwnd
asigna valores a esos campos para su uso posterior mientras myPage
está todavía en el ámbito.
Comunicación con el contenido WPF
Hay dos tipos de comunicación con el contenido de WPF. La aplicación recibe información del contenido de WPF cuando el usuario hace clic en los botones Aceptar o Cancelar. La aplicación también tiene una UI que permite al usuario cambiar diversas propiedades del contenido de WPF, como el tamaño de fuente predeterminado o el color de fondo.
Como se mencionó anteriormente, cuando el usuario hace clic en cualquiera de los botones, el contenido WPF genera un evento OnButtonClicked
. La aplicación adjunta un controlador a este evento para recibir estas notificaciones. Si se hizo clic en el botón Aceptar, el controlador obtiene la información de usuario del contenido de WPF y la muestra en un conjunto de controles estáticos.
void WPFButtonClicked(Object ^sender, MyPageEventArgs ^args)
{
if(args->IsOK) //display data if OK button was clicked
{
WPFPage ^myPage = WPFPageHost::hostedPage;
LPCWSTR userName = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Name: " + myPage->EnteredName).ToPointer();
SetWindowText(nameLabel, userName);
LPCWSTR userAddress = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Address: " + myPage->EnteredAddress).ToPointer();
SetWindowText(addressLabel, userAddress);
LPCWSTR userCity = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("City: " + myPage->EnteredCity).ToPointer();
SetWindowText(cityLabel, userCity);
LPCWSTR userState = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("State: " + myPage->EnteredState).ToPointer();
SetWindowText(stateLabel, userState);
LPCWSTR userZip = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Zip: " + myPage->EnteredZip).ToPointer();
SetWindowText(zipLabel, userZip);
}
else
{
SetWindowText(nameLabel, L"Name: ");
SetWindowText(addressLabel, L"Address: ");
SetWindowText(cityLabel, L"City: ");
SetWindowText(stateLabel, L"State: ");
SetWindowText(zipLabel, L"Zip: ");
}
}
El controlador recibe un objeto de argumento de evento personalizado del contenido WPF, MyPageEventArgs
. La propiedad IsOK
del objeto está establecida en true
si se hizo clic en el botón Aceptar, y en false
si se hizo clic en el botón Cancelar.
Si se hizo clic en el botón Aceptar, el controlador obtiene una referencia al contenido de WPF de la clase de contenedor. A continuación, recopila la información del usuario incluida en las propiedades de contenido WPF asociado y usa los controles estáticos para mostrar la información en la ventana primaria. Dado que los datos de contenido WPF están en forma de cadena administrada, deben serializarse para que pueda usarlos un control Win32. Si se hizo clic en el botón Cancelar, el controlador borra los datos de los controles estáticos.
La interfaz de usuario de la aplicación proporciona un conjunto de botones de radio que permiten al usuario modificar el color de fondo del contenido WPF y varias propiedades relacionadas con la fuente. El ejemplo siguiente es un extracto del procedimiento de ventana de la aplicación (WndProc) y su control de mensajes que establece varias propiedades en diferentes mensajes, incluido el color de fondo. Los demás son similares y no se muestran. Vea el ejemplo completo para obtener detalles y el contexto.
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
//Menu selections
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
//RadioButtons
case IDC_ORIGINALBACKGROUND :
WPFPageHost::hostedPage->Background = WPFPageHost::initBackBrush;
break;
case IDC_LIGHTGREENBACKGROUND :
WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightGreen);
break;
case IDC_LIGHTSALMONBACKGROUND :
WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightSalmon);
break;
Para establecer el color de fondo, obtenga una referencia al contenido WPF (hostedPage
) de WPFPageHost
y establezca la propiedad de color de fondo en el color apropiado. En el ejemplo se usan tres opciones de color: el color original, verde claro y salmón claro. El color de fondo original se almacena como un campo estático en la clase WPFPageHost
. Para establecer los otros dos, se crea un nuevo objeto SolidColorBrush y se pasa al constructor un valor de colores estático desde el objeto Colors.
Implementación de la página WPF
Puede hospedar y usar el contenido de WPF sin ningún conocimiento de la implementación real. Si el contenido de WPF se empaquetó en una DLL independiente, puede que se haya compilado en cualquier lenguaje Common Language Runtime (CLR). A continuación se incluye una breve explicación de la implementación de C++/CLI que se usa en el ejemplo. Esta sección contiene las subsecciones siguientes.
Layout
Los elementos de la interfaz de usuario en el contenido WPF constan de cinco controles TextBox, con controles Label asociados: nombre, dirección, ciudad, estado y código postal. También hay dos controles Button, Aceptar y Cancelar.
El contenido WPF se implementa en la clase WPFPage
. El diseño se controla con un elemento de diseño Grid. La clase hereda de Grid, lo que lo convierte en el elemento raíz del contenido WPF.
El constructor de contenido WPF toma el ancho y el alto necesarios y modifica en consecuencia el tamaño de Grid. A continuación, para definir el diseño básico, crea un conjunto de objetos ColumnDefinition y RowDefinition y los agrega a las colecciones ColumnDefinitions y RowDefinitions de la base de objeto Grid, respectivamente. Esto define una cuadrícula de cinco filas y siete columnas, con las dimensiones determinadas por el contenido de las celdas.
WPFPage::WPFPage(int allottedWidth, int allotedHeight)
{
array<ColumnDefinition ^> ^ columnDef = gcnew array<ColumnDefinition ^> (4);
array<RowDefinition ^> ^ rowDef = gcnew array<RowDefinition ^> (6);
this->Height = allotedHeight;
this->Width = allottedWidth;
this->Background = gcnew SolidColorBrush(Colors::LightGray);
//Set up the Grid's row and column definitions
for(int i=0; i<4; i++)
{
columnDef[i] = gcnew ColumnDefinition();
columnDef[i]->Width = GridLength(1, GridUnitType::Auto);
this->ColumnDefinitions->Add(columnDef[i]);
}
for(int i=0; i<6; i++)
{
rowDef[i] = gcnew RowDefinition();
rowDef[i]->Height = GridLength(1, GridUnitType::Auto);
this->RowDefinitions->Add(rowDef[i]);
}
Después, el constructor agrega los elementos de interfaz de usuario a la Grid. El primer elemento es el texto del título, que es un control Label que se centra en la primera fila de la cuadrícula.
//Add the title
titleText = gcnew Label();
titleText->Content = "Simple WPF Control";
titleText->HorizontalAlignment = System::Windows::HorizontalAlignment::Center;
titleText->Margin = Thickness(10, 5, 10, 0);
titleText->FontWeight = FontWeights::Bold;
titleText->FontSize = 14;
Grid::SetColumn(titleText, 0);
Grid::SetRow(titleText, 0);
Grid::SetColumnSpan(titleText, 4);
this->Children->Add(titleText);
La siguiente fila contiene el control Label de nombre y su control TextBox asociado. Dado que se usa el mismo código para cada par de etiqueta/cuadro de texto, está colocado en un par de métodos privados y se usa para los cinco pares de etiqueta/cuadro de texto. Los métodos crean el control apropiado y llaman a los métodos estáticos Grid y SetColumn de la clase SetRow para colocar los controles en la celda correspondiente. Una vez creado el control, el ejemplo llama al método Add en la propiedad Children de Grid para agregar el control a la cuadrícula. El código para agregar los pares restantes de etiqueta/cuadro de texto es similar. Vea el código de ejemplo para obtener más detalles.
//Add the Name Label and TextBox
nameLabel = CreateLabel(0, 1, "Name");
this->Children->Add(nameLabel);
nameTextBox = CreateTextBox(1, 1, 3);
this->Children->Add(nameTextBox);
La implementación de los dos métodos es la siguiente:
Label ^WPFPage::CreateLabel(int column, int row, String ^ text)
{
Label ^ newLabel = gcnew Label();
newLabel->Content = text;
newLabel->Margin = Thickness(10, 5, 10, 0);
newLabel->FontWeight = FontWeights::Normal;
newLabel->FontSize = 12;
Grid::SetColumn(newLabel, column);
Grid::SetRow(newLabel, row);
return newLabel;
}
TextBox ^WPFPage::CreateTextBox(int column, int row, int span)
{
TextBox ^newTextBox = gcnew TextBox();
newTextBox->Margin = Thickness(10, 5, 10, 0);
Grid::SetColumn(newTextBox, column);
Grid::SetRow(newTextBox, row);
Grid::SetColumnSpan(newTextBox, span);
return newTextBox;
}
Por último, el ejemplo agrega los botones Aceptar y Cancelar y adjunta un controlador de eventos a sus eventos Click.
//Add the Buttons and atttach event handlers
okButton = CreateButton(0, 5, "OK");
cancelButton = CreateButton(1, 5, "Cancel");
this->Children->Add(okButton);
this->Children->Add(cancelButton);
okButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);
cancelButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);
Devolución de datos a la ventana host
Cuando se hace clic en cualquiera de los botones, se genera su evento Click. La ventana host simplemente podría asociar controladores a estos eventos y obtener los datos directamente desde los controles TextBox. En el ejemplo se usa un enfoque algo menos directo. Controla el evento Click dentro del contenido de WPF y, luego, genera un evento personalizado, OnButtonClicked
, para notificar el contenido de WPF. Esto permite al contenido de WPF realizar alguna validación de parámetros antes de notificar al host. El controlador obtiene el texto de los controles TextBox y lo asigna a propiedades públicas, desde las que el host puede recuperar la información.
La declaración de evento, en WPFPage.h:
public:
delegate void ButtonClickHandler(Object ^, MyPageEventArgs ^);
WPFPage();
WPFPage(int height, int width);
event ButtonClickHandler ^OnButtonClicked;
El controlador de eventos Click, en WPFPage.cpp:
void WPFPage::ButtonClicked(Object ^sender, RoutedEventArgs ^args)
{
//TODO: validate input data
bool okClicked = true;
if(sender == cancelButton)
okClicked = false;
EnteredName = nameTextBox->Text;
EnteredAddress = addressTextBox->Text;
EnteredCity = cityTextBox->Text;
EnteredState = stateTextBox->Text;
EnteredZip = zipTextBox->Text;
OnButtonClicked(this, gcnew MyPageEventArgs(okClicked));
}
Establecimiento de propiedades WPF
El host Win32 permite al usuario cambiar varias propiedades de contenido de WPF. Desde el lado de Win32, se trata simplemente de una cuestión de cambiar las propiedades. La implementación en la clase de contenido de WPF es un poco más complicada, porque no hay ninguna propiedad global única que controle las fuentes de todos los controles. En su lugar, se cambia la propiedad adecuada para cada control en los descriptores del conjunto de propiedades. En el ejemplo siguiente se muestra el código de la propiedad DefaultFontFamily
. Al establecer la propiedad, se llama a un método privado que a su vez establece las propiedades FontFamily de los diversos controles.
Desde WPFPage.h:
property FontFamily^ DefaultFontFamily
{
FontFamily^ get() {return _defaultFontFamily;}
void set(FontFamily^ value) {SetFontFamily(value);}
};
Desde WPFPage.cpp:
void WPFPage::SetFontFamily(FontFamily^ newFontFamily)
{
_defaultFontFamily = newFontFamily;
titleText->FontFamily = newFontFamily;
nameLabel->FontFamily = newFontFamily;
addressLabel->FontFamily = newFontFamily;
cityLabel->FontFamily = newFontFamily;
stateLabel->FontFamily = newFontFamily;
zipLabel->FontFamily = newFontFamily;
}
Vea también
.NET Desktop feedback