Cambio el cursor y se ve en blanco y negro

Iniciado por Lekim, 7 Septiembre 2015, 18:40 PM

0 Miembros y 1 Visitante están viendo este tema.

Lekim

Q tal

Mi problema es simple pero al mismo tiempo extraño.  Resulta que quiero hacer que al pasar el puntero por un control, digamos un Label, el puntero cambie.

Lo que hago es cargar en un archivo de recursos un cursor.  Y luego lo llamo del siguiente modo en el evento MouseMove de un Label:

Código (vbnet) [Seleccionar]
Dim curPen As New System.IO.MemoryStream(My.Resources.Resource1.Lapiz)
Cursor.Current = New Cursor(curPen)


Funciona, pero da igual si el cursor es de 8 bits o 24 bits, se ve de color negro o blanco y negro.

No entiendo porqué.

Gracias


Eleкtro

#1
Cita de: Lekim en  7 Septiembre 2015, 18:40 PMFunciona, pero da igual si el cursor es de 8 bits o 24 bits, se ve de color negro o blanco y negro.

No entiendo porqué.

Cómo indica la documentación oficial de la class System.Windows.Forms.Cursor, no soporta cursores con colores distintos a blanco y negro.

Cita de: https://msdn.microsoft.com/en-us/library/system.windows.forms.cursor.aspxThe Cursor class does not support animated cursors (.ani files) or cursors with colors other than black and white.

Tienes que hechar mano de la WinAPI, particulármente la función LoadCursorFromFile, la cual aparte de solventar el problema de los colores también permite utilizar cursores animados:
LoadCursorFromFile function - MSDN




He desarrollado la siguiente class para simplificar la tarea y automatizar la carga del cursor.

Código fuente:
Código (vbnet) [Seleccionar]
' ***********************************************************************
' Author   : Elektro
' Modified : 07-September-2015
' ***********************************************************************
' <copyright file="CursorUtil.vb" company="Elektro Studios">
'     Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************

#Region " Imports "

Imports System
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

#End Region

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Contains related cursor utilities.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class CursorUtil

#Region " P/Invoking "

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Platform Invocation methods (P/Invoke), access unmanaged code.
   ''' This class does not suppress stack walks for unmanaged code permission.
   ''' <see cref="System.Security.SuppressUnmanagedCodeSecurityAttribute"/> must not be applied to this class.
   ''' This class is for methods that can be used anywhere because a stack walk will be performed.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <remarks>http://msdn.microsoft.com/en-us/library/ms182161.aspx</remarks>
   ''' ----------------------------------------------------------------------------------------------------
   Private NotInheritable Class NativeMethods

#Region " Functions "

       ''' ----------------------------------------------------------------------------------------------------
       ''' <summary>
       ''' Creates a cursor based on data contained in a file.
       ''' </summary>
       ''' ----------------------------------------------------------------------------------------------------
       ''' <param name="filepath">
       ''' The source of the file data to be used to create the cursor.
       ''' The data in the file must be in either .CUR or .ANI format.
       ''' </param>
       ''' ----------------------------------------------------------------------------------------------------
       ''' <returns>
       ''' If the function is successful, the return value is an <see cref="IntPtr"/> to the new cursor.
       ''' If the function fails, the return value is <see cref="IntPtr.Zero"/>.
       ''' To get extended error information, call <see cref="Marshal.GetLastWin32Error"/>.
       ''' </returns>
       ''' ----------------------------------------------------------------------------------------------------    
       ''' <remarks>
       ''' <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms648392%28v=vs.85%29.aspx"/>
       ''' </remarks>
       ''' ----------------------------------------------------------------------------------------------------
       <DllImport("User32.dll", CharSet:=CharSet.Ansi, BestFitMapping:=False, ThrowOnUnmappableChar:=True, SetLastError:=True)>
       Public Shared Function LoadCursorFromFile(
               ByVal filepath As String
       ) As IntPtr
       End Function

#End Region

   End Class

#End Region

#Region " Constructors "

   ''' <summary>
   ''' Prevents a default instance of the <see cref="CursorUtil"/> class from being created.
   ''' </summary>
   Private Sub New()
   End Sub

#End Region

#Region " Public Methods "

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Creates a cursor based on data contained in a managed .Net resource.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <param name="resource">
   ''' The resource.
   ''' </param>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <returns>
   ''' <see cref="System.Windows.Forms.Cursor"/>.
   ''' </returns>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerStepThrough>
   <DebuggerHidden>
   Public Shared Function LoadCursorFromResource(ByVal resource As Byte(),
                                                 Optional cleanTempFile As Boolean = True) As Cursor

       Dim tmpFilepath As String = String.Format("{0}.ani", Path.GetTempFileName)

       Try
           Using fs As New FileStream(tmpfilepath, FileMode.Create, FileAccess.Write, FileShare.Read)
               fs.Write(resource, 0, resource.Length)
           End Using

           Dim result As IntPtr = NativeMethods.LoadCursorFromFile(tmpFilepath)
           Dim win32Err As Integer = Marshal.GetLastWin32Error

           If result = IntPtr.Zero Then
               Throw New Win32Exception([error]:=win32Err)
           Else
               Return New Cursor(result)
           End If

       Catch ex As Exception
           Throw

       Finally
           If (cleanTempFile) AndAlso (File.Exists(tmpFilepath)) Then
               File.Delete(tmpFilepath)
           End If

       End Try

   End Function

#End Region

End Class


Modo de empleo:
Código (vbnet) [Seleccionar]
Public NotInheritable Class Form1 : Inherits Form

   Private Sub Label1_MouseEnter(ByVal sender As Object, ByVal e As EventArgs) _
   Handles Label1.MouseEnter

       Static cur As Cursor

       If cur Is Nothing Then
           cur = CursorUtil.LoadCursorFromResource(My.Resources.MyCursor)
       End If

       ' Asegurarse de liberar el objeto cuando sea necesario...

       DirectCast(sender, Label).Cursor = cur

   End Sub

   Private Sub Label1_MouseLeave(ByVal sender As Object, ByVal e As EventArgs) _
   Handles Label1.MouseLeave

       DirectCast(sender, Label).Cursor = Cursors.Default

   End Sub

End Class


Recursos usados para las pruebas:


Metro X Cursor Set - DeviantArt

Saludos








Lekim

#2
Gracias, el código va perfecto.

Tan sólo un problema me ha dado y es el siguiente. He usado un Form simple en VisualBasic.Net 2010 y tras copiar y pegar tu código ocurría el siguiente error:

La clase Form1 se puede diseñar, per no es la primera clase de archivo.
Visual Studio requiere que los diseñadores utilicen la primera clase del archivo.
Mueva el código de clase para convertirla en la primera clase del archivo y vuelva a intetar cargar el diseñador de nuevo.


No se podía ver el diseño del form.

Por ello,si me lo permites, he reducido y cambiado el orden de tu código. También he usado el evento MouseMove en lugar de Label1_MouseEnter y Label1_MouseLeave, ya que el Label que uso en mi programa se crea en tiempo real y es una matriz de control. Creo el evento mediante código y prefiero crear sólo un evento en lugar de dos. Funciona igual. También he cambiado "{0}.ani" por "{0}.cur" ya que mi cursor no es animado. Espero que no te importe.



Código (vbnet) [Seleccionar]
Imports System
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class Form1

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

   End Sub

   Private Sub Label1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Label1.MouseMove
       Static cur As Cursor

       If cur Is Nothing Then
           cur = CursorUtil.LoadCursorFromResource(My.Resources.Resource1.Cursor1)
       End If

       ' Asegurarse de liberar el objeto cuando sea necesario...

       DirectCast(sender, Label).Cursor = cur
   End Sub
End Class


Public NotInheritable Class CursorUtil
   <DllImport("User32.dll", CharSet:=CharSet.Ansi, BestFitMapping:=False, ThrowOnUnmappableChar:=True, SetLastError:=True)>
   Public Shared Function LoadCursorFromFile(ByVal filepath As String) As IntPtr
   End Function

   Public Shared Function LoadCursorFromResource(ByVal resource As Byte(), Optional ByVal cleanTempFile As Boolean = True) As Cursor

       Dim tmpFilepath As String = String.Format("{0}.cur", Path.GetTempFileName)

       Try
           Using fs As New FileStream(tmpFilepath, FileMode.Create, FileAccess.Write, FileShare.Read)
               fs.Write(resource, 0, resource.Length)
           End Using

           Dim result As IntPtr = LoadCursorFromFile(tmpFilepath)
           Dim win32Err As Integer = Marshal.GetLastWin32Error

           If result = IntPtr.Zero Then
               Throw New Win32Exception([error]:=win32Err)
           Else
               Return New Cursor(result)
           End If

       Catch ex As Exception
           Throw

       Finally
           If (cleanTempFile) AndAlso (File.Exists(tmpFilepath)) Then
               File.Delete(tmpFilepath)
           End If

       End Try

   End Function

End Class



Esperaba no tener que usar una función API. No entiendo como no se permite usar cursores en color, la verdad.


Eleкtro

#3
Cita de: Lekim en  8 Septiembre 2015, 16:51 PMTan sólo un problema me ha dado y es el siguiente. He usado un Form simple en VisualBasic.Net 2010 y tras copiar y pegar tu código ocurría el siguiente error:

La clase Form1 se puede diseñar, per no es la primera clase de archivo.
Visual Studio requiere que los diseñadores utilicen la primera clase del archivo.
Mueva el código de clase para convertirla en la primera clase del archivo y vuelva a intetar cargar el diseñador de nuevo.


No se podía ver el diseño del form.

Por ello,si me lo permites, he reducido y cambiado el orden de tu código.

Corrígeme si me equivoco al hacer suposiciones, pero deduzco que lo que hiciste fue simplemente copiar y pegar en la class principal el código de ambas classes, de esta forma:

Form1.vb
Código (vbnet) [Seleccionar]
Class CursorUtil
...
End lass

Class Form1
...
End Class


Por supuesto eso no te podría haber funcionado y el modo en el que lo corregiste fue modificando el orden de las definiciones de esas classes, pero lo que realmente debes hacer es crear una nueva class, me refiero a un nuevo archivo class.vb donde debes definir la class CursorUtil.




Cita de: Lekim en  8 Septiembre 2015, 16:51 PMEsperaba no tener que usar una función API. No entiendo como no se permite usar cursores en color, la verdad.

Si, deja bastante que desear la ausencia de compatibilidad con cursores regulares, pero así es la realidad, lo cierto es que la tecnología WindowsForms se puede considerar casi-deprecada ya que es primitiva en muchos otros aspectos gráficos y hay que estar hackeando constantemente la UI para realizar modificaciones estéticas comunes, ese es el gran inconveniente que conlleva utilizar WinForms.

Si realmente quieres evitar este tipo de problemas entonces la mejor opción sería migrar a la tecnología WPF (WindowsPresentationFoundation) ya que es la que actuálmente está en constante desarrollo y actualización por parte de Microsoft, en comparación con Windowsforms donde, en lo referente a la UI y las classes u otros miembros relacionados con la UI ya no veremos ninguna mejora/actualización cómo por ejemplo eso, la esencial compatibilidad de cursores.

En WPF el equivalente a la class System.Windows.Forms.Cursor sería la class System.Windows.Input.Cursor, la cual por supuesto soporta cursores estáticos y animados a 32-bit depth en full HD x'D.

Saludos!








Lekim

Cita de: Eleкtro en  9 Septiembre 2015, 00:05 AM
Corrígeme si me equivoco al hacer suposiciones, pero deduzco que lo que hiciste fue simplemente copiar y pegar en la class principal el código de ambas classes, de esta forma:
No te equivocas jeje, es verdad, tendría que haberlo metido en un módulo. Fallo de novatos  :xD

En cuanto a lo de WPF. Pues he hecho algunas pruebas y lo he usado.. muy poco no, nada en absoluto. Mi primeros intentos fue porque leí maravillas con respecto a su capacidad gráfica y que podías personalizar los controles. Eso es algo que a mí me gusta hacer. El problema es que no he conseguido personalizar los controles en general, es decir, no tener que estar estableciendo la configuración gráfica para cada control, y de este modo establecer una aspecto gráfico uniforme y cosntante en toda la aplicación. He encontrado ejemplos en la red pero no me han funcionado, supongo que no habré hecho algo bien.

Más adelante, un día de estos crearé un nuevo tema y preguntaré sobre esto en cuestion, poniendo el código y el método que he usado y haber si alguien me ayuda con este tema. Mientras tanto usaré el form clásico. Poco a poco se va llenando el bote.

Saludos