Hace algún tiempo, con alguna actualización Insider de Windows, dejó de funcionarme la autenticación por reconocimiento facial, conocida como Windows Hello, con lo que últimamente no he podido disfrutar de la comodidad de que se desbloquee el equipo al sentarme delante de él. Al dejar de funcionarme intenté añadir un nuevo perfil por si el problema estaba en que no había suficientes para comparar pero, al intentarlo, recibí un error que decía “It looks like you’ve already setup Windows Hello on another account” y que me imposibilitaba no sólo añadir nuevos perfiles sino usar Windows Hello en sí.
Tras varias semanas de buscar soluciones inexistentes y tratar de probar todo tipo de cosas como quitar Windows Hello y el PIN, ejecutar comandos de powershell para resetear la configuración de las Apps de Windows, etc… al final encontré la solución en un foro de Microsoft donde un usuario ha compartido una guía de pasos que ha tomado y que le han servido Solución
Mi solución
En mi caso, me ha bastado con ejecutar el paso 3 “Browse to C:\Windows\System32\WinBioDatabase\ and delete all entries, I had two for two user accounts.” para que comenzara a funcionarme de nuevo Windows Hello.
Es bastante común en las aplicaciones que muestran listados de registros, disponer de un menú contextual que extienda las opciones disponibles a realizar sobre cada registro, o simplemente que contenga algunas de ellas que podamos considerar como más usadas para que el usuario no tenga que desplazarse por la pantalla en busca de la opción necesaria.
Para esto, prácticamente todos los controles de WPF disponen de menú contextual, fácilmente gestionable pero que presenta algunos retos cuando estamos trabajando con el patrón MVVM ya que, el Binding se tiene que resolver con el ViewModel que contiene las acciones para que los comandos asociados a las opciones funcionen correctamente, así que vamos a ver paso a paso cómo podemos solventarlo.
Montando el Menú Contextual en nuestro DataGrid
Montar el menú contextual es una labor muy sencilla ya que, con sólo acceder a la propiedad ContextMenu del DataGrid podríamos tenerlo pero, como lo queremos a nivel de registros, debemos asociárselo a cada una de las filas del DataGrid y no al DataGrid en sí mismo tal y como se muestra en el siguiente snippet de código.
Como se puede observar en el código XAML, tendremos una colección asignada al DataGrid mediante un Binding a una colección de objetos de nuestro ViewModel. En el próximo artículo os hablaré sobre cómo establecer MVVM en WPF, así que de momento estableceremos el DataContext de nuestra vista de forma directa añadiendo el ViewModel tal y como se puede apreciar antes de comenzar el Grid con la etiqueta “<Window.DataContext>…”. Posteriormente definimos el ContextMenu como un recurso del DataGrid y le asignaremos un valor a la propiedad x:Name que debe ser único en el contexto del DataGrid y que en mi ejempo es “MyContextMenu” para después crear un estilo para las filas del DataGrid mediante la etiqueta “<DataGrid.RowStyle>…” donde establecemos mediante un “<Setter>” la propiedad ContextMenu de las filas y le asignamos el ContextMenu que definimos en los recursos del DataGrid haciendo uso de su Name.
NOTA: Como buena práctica, es importante asignar un valor a la propiedad x:Name de aquellos elementos más relevantes para poder acceder a ellos en Bindings y Storyboards que establezcamos en nuestro código XAML.
De esta forma, al ejecutar la aplicación, ya tendremos disponible un menú contextual en cada uno de los registros del DataGrid tal y como se puede ver en la siguiente imagen aunque de momento no ejecutará accíón alguna.
Añadiendo acciones a las opciones del menú contextual
En este punto es donde comienza a complicarse la situación porque debemos establecer un contexto para poder hacer un Binding con nuestro ViewModel y así poder ejecutar los comandos que contiene. Buscando información al respecto, encontré este artículo sobre ContextMenu a nivel de filas https://blog.gisspan.com/2012/11/contextmenu-for-wpf-datagrid-on-row.html que ofrece una buena explicación pero da por hecho que cada uno de los registros está asociado a un ViewModel en sí y que, por lo tanto, cada uno de ellos contiene las acciones a ejecutar. Pero en realidad, el caso es más complejo si estamos siguiendo el patrón MVVM de una forma más estricta y que, por lo tanto, disponemos de un ViewModel global para la vista que contiene toda la información que queremos mostrar en pantalla en forma de propiedades además de los comandos que se pueden ejecutar. Es decir, este ViewModel contiene una colección que no necesariamente tiene que ser también de ViewModels y que, de hecho, por rendimiento no debería serlo, además de los comandos que se pueden ejecutar tal y como muestro en el código a continuación.
public class FakeViewModel : INotifyPropertyChanged
{
public List<MyGridModel> MyGridModelCollection
{
get; set;
}
private DelegateCommand<object> myCommand;
public ICommand MyCommand => myCommand;
public FakeViewModel()
{
MyGridModelCollection = InitializeCollection();
this.myCommand = new DelegateCommand<object> (MyCommandMethod);
}
private void MyCommandMethod(object parameter)
{
var myModel = parameter as MyGridModel;
MessageBox.Show("You selected a command");
}
private List<MyGridModel> InitializeCollection()
{
var collection = new List<MyGridModel>
{
new MyGridModel { Title = "Item #1", Desc="Desc for item #1 ", Created=System.DateTime.Now },
new MyGridModel { Title = "Item #2", Desc="Desc for item #2 ", Created=System.DateTime.Now },
new MyGridModel { Title = "Item #3", Desc="Desc for item #3 ", Created=System.DateTime.Now },
new MyGridModel { Title = "Item #4", Desc="Desc for item #4 ", Created=System.DateTime.Now },
new MyGridModel { Title = "Item #5", Desc="Desc for item #5 ", Created=System.DateTime.Now },
};
return collection;
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
var Handler = PropertyChanged;
Handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Es importante tener en cuenta que los ViewModels deberían implementar la interfaz INotifyPropertyChanged para poder comunicar a la vista cualquier cambio que se produzca en las propiedades que están enlazadas con Binding.
Una vez que tenemos nuestro ViewModel, vamos a enlazar nuestros comandos a través de la propiedad Commandque tienen los elementos del ContextMenu de la siguiente forma.
Como se puede ver, estamos haciendo un Binding al comando definido en el ViewModel. ¿Funcionará? La respuesta es que No porque el contexto de cada una de las filas es el modelo que tiene que representar, es decir, una instancia de tipo MyGridModel que no contiene el comando, con lo que no encontrará lo que debe ejecutar.
Estableciendo el contexto adecuado
Para que sea posible encontrar el comando a ejecutar en cada una de las opciones del menú contextual, es necesario establecer su contexto real, es decir, el ViewModel global. Hay varios workarounds un poco complejos o mal pensados desde mi punto de vista, así que voy a dar mi versión óptima o la que creo que es más óptima. En primer lugar, le indicaremos al menú contextual que su contexto debe ser el ViewModel de la vista y esto lo haremos estableciendo el mismo que el del propio DataGrid. Esto implica que, al ser el contexto de cada fila el ViewModel completo, debemos pasar como parámetro del comando el elemento actual para que la acción se pueda ejecutar sobre ese elemento en cuestión. Estos cambios de contexto son los que cuesta algo más entender y establecer, pero se ven de forma clara en el código.
Como podéis ver, el parámetro del comando será el elemento seleccionado del DataGrid ya que, al hacer click con el botón derecho sobre el elemento, éste quedará seleccionado.
Con esta solución, ya podremos ejecutar los comandos necesarios dentro de nuestro ContextMenuy seguir trabajando con nuestro ViewModel respetando las características de MVVM.
Descarga
Si queréis ver el código completo, podéis acceder a mi repositorio de GitHub y descargároslo libremente
Cuando estamos realizando la navegación dentro de un Frame en una aplicación WPF, de forma automática nos aparecerá la barra de navegación que ocasiones puede que no deseemos mantenerla pese a las ventajas que nos podría aportar, ya sea porque el diseño que debemos implementar no contemple esa barra de navegación o simplemente porque no queremos que el usuario la use y proveemos nosotros mismos la navegación funcional requerida.
Mientras desarrollaba la aplicación Torch8 me encontré con estas dos formas de controlar el flash de la cámara pero, ¿cuál es la adecuada para qué momento y cómo debo usarla?
Si vamos a la documentación podemos observar que la ayuda para tomar esta decisión es mínima y que depende en gran medida de la interpretación que hagamos sobre la descripción.
TorchControl : Provides functionality for controlling the torch LED settings on a capture device.
FlashControl : Provides functionality for controlling the flash settings on a capture device.
Y os preguntaréis… WTF!! Pero si es análogo!! Pues no exactamente. Digamos que la diferencia es que Torch está pensado simplemente para alumbrar porque sí y Flash está pensando para realizar la acción de flash durante una captura de una foto o un vídeo aunque en definitiva, sea prácticamente lo mismo. De hecho, FlashControl contiene alguans propiedades más como RedEyeReduction y RedEyeReductionSupported que nos dan esa misma idea.
Sea cual sea lo que decidamos usar y que vaya acorde con nuestra aplicación, su uso es muy similar, necesitando únicamente una instancia de MediaCapture desde la que obtener el acceso al controlador tal y como muestro en los ejemplos
TorchControl
public void TurnTorch(bool on)
{
if (mediaCapture.VideoDeviceController.TorchControl.Supported)
{
if (on)
{
if (mediaCapture.VideoDeviceController.TorchControl.PowerSupported)
mediaCapture.VideoDeviceController.TorchControl.PowerPercent = 100;
mediaCapture.VideoDeviceController.TorchControl.Enabled = true;
}
else
mediaCapture.VideoDeviceController.TorchControl.Enabled = false;
}
}
FlashControl
public void TurnFlash(bool on)
{
var flashControl = mediaCapture.VideoDeviceController.FlashControl;
if (flashControl.Supported)
{
if (on)
{
if (flashControl.PowerSupported)
flashControl.PowerPercent = 100;
#if WINDOWS_PHONE_APP
if (flashControl.AssistantLightSupported)
flashControl.AssistantLightEnabled = true;
#endif
if (flashControl.RedEyeReductionSupported)
{
flashControl.RedEyeReduction = true;
}
flashControl.Enabled = true;
}
else
flashControl.Enabled = false;
}
}
Como podéis observar, con el control del flash se pueden realizar más tareas que sólo tienen sentido en la captura de una imagen pero, que en el caso de mi aplicación de linterna no tendrían razón de ser, por eso, debéis usar la que se adapte mejor a vuestra aplicación en cada momento.
Notas importantes
Como siempre, recordad liberar los recursos de la cámara como os indiqué en el artículo anterior y, como apunte a este artículo, no os asustéis si el led no se enciende cuando estéis desarrollando, simplemente, tened en cuenta que si el dispositivo está conectado al PC (o a la corriente) el led no se activará con este código
Supongamos que estamos realizando una aplicació y necesitamos ver lo que estamos enfocando con la cámara ya sea para iniciar una grabación, tomar una foto o simplemente, como en mi caso para hacer una linterna con cámara (Torch8). Dado que ya tenemos disponible el desarrollo para Windows 10 (desktop y mobile) la información escasea acerca de esta temática para la plataforma Windows Phone 8.1 y Windows 8.1, así que he recopilado lo que he encontrado al respecto y os lo condensaré en este artículo.
Seleccionar cámara
Para empezar, debemos seleccionar la cámara del dispositivo dado que podemos tener delantera, trasera o las dos.
// Get all the cameras DeviceInformationCollection cameras = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
// Selecting frotal camera DeviceInformation frontCam = (from webcam in cameras where webcam.EnclosureLocation != null && webcam.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front select webcam).FirstOrDefault();
// Selecting rear camera DeviceInformation rearCam = (from webcam in cameras where webcam.EnclosureLocation != null && webcam.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back select webcam).FirstOrDefault();
Inicializar parámetros de captura
Una vez tengamos la cámara deseada, tendremos que inicializar la captura de vídeo con los parámetros que necesitemos como por ejemplo el código que muestro a continuación
// Initialize MediaCapture parameters await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings { VideoDeviceId = rearCam.Id, // Using rear camera AudioDeviceId = "", // Don’t capture audio StreamingCaptureMode = StreamingCaptureMode.Video, // We want to capture video PhotoCaptureSource = PhotoCaptureSource.VideoPreview // Capture mode (Auto | Photo | VideoPreview)
});
Comenzar a obtener visualización de la cámara
Llegados a este punto, ya podemos visualizar lo que enfoquemos con la cámara en nuestro dispositivo y, para ello necesitamos tener preparado el control en la vista XAML y referenciarlo desde el código C#
// Set the source of the CaptureElement to your MediaCapture PreviewControl.Source = mediaCapture;
// Start Preview of Video.
await mediaCapture.StartPreviewAsync();
Más información
Toda esta información y podéis encontrarla con mayor profundidad en este enlace de MSDN
Nota Importante
Debeis recordar que es absolutamente necesario liberar los recursos de la cámara al cerrar/suspender la aplicación ya que si no, puede haber conflictos con otras aplicaciones e incluso la nuestra no funcionará como esperamos, para ello, en el enlace que os he puesto tenéis a vuestra dispocisión una muestra de código para limpiar los recursos
public async Task CleanupCaptureResources()
{
if (IsRecording && MediaCapture != null)
{
await MediaCapture.StopRecordAsync();
IsRecording = false;
}
if (IsPreviewing && MediaCapture != null)
{
await MediaCapture.StopPreviewAsync();
IsPreviewing = false;
}
if (MediaCapture != null)
{
if (PreviewElement != null)
{
PreviewElement.Source = null;
}
MediaCapture.Dispose();
}
}
Esta limpieza debéis llevarla a cabo al menos al suspender la aplicación tal y como os copio y pego de la documentación
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//cleanup camera resources
await CleanupCaptureResources();
deferral.Complete();
}
No obstante debéis consultar en profundidad la documentación acerca de los estados y el ciclo de vida de las aplicaciones Windows Phone 8.1 o incluso estudiar la posibilidad de manejar la suspensión en la vista tal y como se explica en este artículo de MSDN.