WPF Zoom

Una de las primeras cosas que hice cuando me explicaron el objeto VisualBrush y que me llevo bastante tiempo hacerlo fue una lupa para que cuando mueva el ratón todo lo que se vea dentro de la lupa se vea agrandado es decir la sensación de lupa.

 

Del XAMl que os voy a poner ahora fijaros solamente en el objeto VisualBrush, el otro XAML es para hacer la lupa

 

<Canvas Name="magnifierCanvas" IsHitTestVisible="False">
        <Line StrokeThickness="30" X1="200" Y1="200" X2="300" Y2="300">
            <Line.Stroke>
                <LinearGradientBrush StartPoint="0.78786,1" EndPoint="1,0.78786">
                    <GradientStop Offset="0" Color="DarkGreen" />
                    <GradientStop Offset="0.9" Color="LightGreen" />
                    <GradientStop Offset="1" Color="Green" />
                </LinearGradientBrush>
            </Line.Stroke>
        </Line>
        <Ellipse Width="250" Height="250" Fill="White" />
        <Ellipse Width="250" Height="250" Name="magnifierEllipse" StrokeThickness="4">
            <Ellipse.Fill>
                <VisualBrush ViewboxUnits="Absolute" Viewbox="0,0,50,50"
                         ViewportUnits="RelativeToBoundingBox" Viewport="0,0,1,1" />
            </Ellipse.Fill>
            <Ellipse.Stroke>
            	<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
            		<GradientStop Color="#FF555555" Offset="0"/>
            		<GradientStop Color="#FFEEEEEE" Offset="1"/>
            	</LinearGradientBrush>
            </Ellipse.Stroke>
        </Ellipse>
        <Ellipse Canvas.Left="2" Canvas.Top="2" StrokeThickness="4" Width="246" Height="246">
            <Ellipse.Stroke>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="#555" />
                    <GradientStop Offset="1" Color="#EEE" />
                </LinearGradientBrush>
            </Ellipse.Stroke>

        </Ellipse>
    </Canvas>
 
 

Viewbox y Viewport son dos propiedades de ImageBursh, DrawingBrush, TileBrush and VisualBrush elements in Windows Presentation Foundation.

Estos dos atributos te permiten recortar las imágenes y figuras y ponerlas escaladas. Viewbox representa un área en la imagen original o el dibujo que queremos mostrar en nuestra aplicación. ViewPort representa el área en nuestra aplicación donde vamos a mostrar la imagen del ViewBox. ViewboxUnits determina que si los valores son relativos al Viewbox (RelativeToBoundingBox) o absolutos  (Absolute).

Para utilizar este control lo que hacemos es que en su evento Loaded

 if (Visibility == Visibility.Visible)
            {
                window = Helper.FindAncestorOrSelf<Window>(this);
                if (window != null)
                {
                    window.PreviewMouseMove += new MouseEventHandler(window_PreviewMouseMove);
                    VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
                    b.Visual = (Canvas)window.FindName("canMainContent");
                }
            }
 

Miro a ver si esta dentro de un objeto Window y si esta le asocio el evnto PreviewMouseMove y el objeto VisualBrush del control asignándole un objeto de mi ventana que se llama canMainContent, que es el obejto que quiero ampliar.

En el evento PreviewMoueseMove

 void window_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
            Point pos = e.MouseDevice.GetPosition(window);
            Rect viewBox = b.Viewbox;
            double xoffset = viewBox.Width / 2.0;
            double yoffset = viewBox.Height / 2.0;
            viewBox.X = pos.X - xoffset;
            viewBox.Y = pos.Y - yoffset;
            viewBox.Width = 150.0;
            viewBox.Height = 150.0;
            b.Viewbox = viewBox;
            Canvas.SetLeft(this, pos.X - magnifierEllipse.Width / 2);
            Canvas.SetTop(this, pos.Y - magnifierEllipse.Height / 2);
        }

Recojo el área alrededor del ratón para ampliarlo en el VisualBrush definido.

Al a probarlo

WPF – Encontrar Ascendientes y Descendientes en el Arbol visual

Vaya titulito, y es que no sabia como llamarle así que me he decidido por un titulo largo, al grano este post viene derivado de una pregunta de los foros, la pregunta resumida era como recorrerse el árbol visual de elementos de WPF(algo que ya hemos explicado) para encontrar el padre de un tipo determinado que tiene un objeto.

Por ejemplo coger la instancia de la ventana que ha instanciado mi UserControl, esto seria posible con la siguiente función 

public static T FindAncestorOrSelf<T>(DependencyObject obj) where T : DependencyObject
        {
            while (obj != null)
            {
                T objTest = obj as T;
                if (objTest != null) return objTest;
                obj = VisualTreeHelper.GetParent(obj);
            }
            return null;
        }

y su llamada seria

 

 window = Helper.FindAncestorOrSelf<Window>(this);

 

Como veis me recorro con la función VisualTreeHelper.GetParent el padre del objeto y así voy subiendo sobre el árbol visual hasta encontrar un objeto de tipo Window.

Y para los descendientes sería

 

public Visual GetDescendantByType(Visual element, Type type)
{
if (element == null) return null;
if (element.GetType() == type) return element;
Visual foundElement = null;
if (element is FrameworkElement)
{
(element as FrameworkElement).ApplyTemplate();
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = GetDescendantByType(visual, type);
if (foundElement != null)
break;
}
return foundElement;
}

En el siguiente post la utilizaremos

StringFormat en WPF 3.5 SP1

Una de las nuevas cosas que hay en el SP1 de 3,5 para WPF es StringFormat a la hora de hacer DataBinding que nos va a permitir formatear nuestros datos sin tener que realizar Converters. La clase esta definida por String.Format(IFormatProvider, String, Object[]). y solo se aplica en una dirección de la fuente al destino.

 

Por ejemplo

<Binding ElementName="myComboBox" Path="SelectedItem.Content" StringFormat="{}{0:C}"/>

 

Nos formateara el item seleccionado de un combo a la moneda

 

Para fecha

 

<TextBlock Text="{Binding StringFormat='HH:MM d/M/yyyy'}"/>

Cargar Imagenes Thumnbail

El otro día haciendo una pequeña aplicación para ver la lista de fotos que tengo de mi hijo que os podéis imaginar que son miles en los casi tres años que tiene, vi que al cargarlos en un ListView me tardaba bastante, también lo achaque a que algunas fotos (la mayoría) son de mas de 5 MB.

Hacia una carga del las fotos de mi disco duro

 

 var fotos = from d in new DirectoryInfo(@"d:fotos").GetDirectories()
                        from f in d.GetFiles()
                        where f.Extension.Contains("JPG")
                        orderby d.Name
                        select new { ImagePath = f.FullName, dirName = d.Name };
            DataContext = fotos;

Yo utilizaba una ListView de este tipo

<ListView ItemsSource="{Binding}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding Path=ImagePath, Converter={StaticResource ImageConverter}}" /> 
                </DataTemplate> 
            </ListView.ItemTemplate> 
        </ListView>

Con el típico Converter de imágenes que ya hemos explicado

 public sealed class ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                return new BitmapImage(new Uri((string)value));
            }
            catch
            {
                return new BitmapImage();
            }
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

La verdad es que tardaba bastante y además quería que se visualizase en formato thumbail,  me puse a buscar si habría alguna manera mas rápida de hacerlo y si hay, nos tenemos que basar en la clase BitmapImage. Esta clase tiene la propiedad DecodePixelWidth que obtiene o establece el ancho, en píxeles, en el que se decodifica la imagen, en este caso pasamos las imagenes a100 poxels que para un thumbail es mas que suficiente.

De manera que le  Converter pasaba a

public sealed class ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.DecodePixelWidth = 100;
            bi.CacheOption = BitmapCacheOption.OnLoad;
            bi.UriSource = new Uri(value.ToString());
            bi.EndInit();
            return bi;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

La carga era mucho mas rápida y eficiente, además de mostrar en el tamaño adecuado. Así que ya sabéis si queréis mostrar thumbails de imágenes utilizar esta propiedad.

Pero para poner una pega ese listbox me muestra por defecto una lista hacia abajo, si quisiera cambiar la propiedad ItemsPanelTemplate para poner un WrapPanel y me las muestre en horizontal que creéis que pasara? Probarlo os llevareis una sorpres(para probarlo utilizar mas de 1000 archivos una carga masiva) En el siguiente post lo contare

 

 

 

Dynamic Resource y StaticResource Diferencias

Otra de las preguntas mas demandadas es la diferencia que hay entre StaticResource y DynamicResource en WPF. La diferencia es clara: StaticResource son evaluados en tiempo de carga del ejecutable, mientras que DynamicResource se evalúa en tiempo de ejecución, cuando se necesita.

Pero lo mejor es verlo con un ejemplo para ver cuando nos puede dar problemas, el ejemplo va a constar de un recurso que va a ser una brocha y vamos a tener dos botones que vamos a asociar esta brocha una con StaticResource y otro con DynamicResource

 

El XAML sería

 

<Window.Resources>
        <SolidColorBrush Color="Red" x:Key="BrochaRoja"></SolidColorBrush>
    </Window.Resources>

    <StackPanel>

        <Button Background="{StaticResource BrochaRoja}">Static Resource</Button>

        <Button Background="{DynamicResource BrochaRoja}">Dynamic Resource</Button>

        <Button x:Name="CambiarBrocha" Click="CambiarBrocha_Click">Cambiar Brocha</Button>

        <Button x:Name="CambiarRecurso" Click="CambiarRecurso_Click">CambiarRecurso</Button>

    </StackPanel>

 

Si nos vamos al código

 

 SolidColorBrush brochaRoja;

        SolidColorBrush otraBrocha;



        public Window1()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Window1_Loaded);
        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            brochaRoja = Resources["BrochaRoja"] as SolidColorBrush;

            otraBrocha = new SolidColorBrush(Colors.Blue);


        }


        private void CambiarBrocha_Click(object sender, RoutedEventArgs e)
        {
            if (brochaRoja.Color == Colors.Red)
            {

                brochaRoja.Color = Colors.Blue;

            }

            else
            {

                brochaRoja.Color = Colors.Red;

            }


        }

        private void CambiarRecurso_Click(object sender, RoutedEventArgs e)
        {
            if (FindResource("BrochaRoja") as SolidColorBrush == brochaRoja)
            {

                Resources["BrochaRoja"] = otraBrocha;

            }

            else
            {

                Resources["BrochaRoja"] = brochaRoja;

            }


        }

La pantalla inicial sera

 

image

 

En el botón de cambiar brocha cambiamos el color de la brocha que hemos cargado a través del recurso y que creéis que pasara con la definición de arriba, se pondrán los dos botones azules o uno seguirá azul  y otro se quedara rojo o no pasara nada?

Nos quedara…

 

image

 

 

Si pulsamos cambiar recurso nos quedara

 

image

 

El botón con recurso estático no cambiará porque el recurso ya ha sido evaluado en tiempo de carga.

 

Para elegir entre StaticResource o DynamicREsource nos podremos fijar en

 

StaticResource requiere menos tiempo de CPU pero hace que la carga de la aplicación sea mas lenta, de maneras que si tienes muchos recursos y quieres que la aplicación se cargue rápido deberás balancear entre Static y Dynamic.

Si los recursos son desconocidos en tiempo de compilación deberas utilizar Dynamic, hay dos casos tipicos para esto

  1. Cambio de tema de tu aplicación en runtime
  2. Cuando haces binding a recursos del sistema como fuentes o colores, si quieres que tu aplicación responda a los cambios de configuración de windows

 

 

 

Drag & Drop en WPF

Una de las preguntas mas recurrentes en los foros, cursos.. es como hacer Drag &Drop en WPF, hay muy buenos y muchísimos ejemplos en la red, yo voy a intentar explicarlo sin un gran ejemplo pero que se entiendan todos los conceptos.

Si leemos el capitulo de MSDN relativo al drag&drop , la secuencia de un drag &drop es la siguiente

 

1. Draging es inicializado por la llamada al método DoDragDrop

El metodo DoDragDrop tiene dos parámetros:

  • data, se especifica los datos a pasar
  • allowedEffects, se especifica cual operación (copiar y/o mover) son permitidas

2. En este momento se lanza el evento GiveFeedback que te permite por ejemplo displayar un puntero del ratón diferente durante el drag.

3. Cualquier control que tenga la propiedad AllowDrop a True es un potencial control destino del drag & drop

4. Cuando el ratón pasa por el control , el evento DragEnter es lanzado, con el metodo GetDataPresent compruebas que los datos que estan siendo arrastrados cumplen  el formato para tu control y la propiedad Effect es usada para mostrar un adecuado cursor.

5. Si el usuario suelta el arrastre en un control que cumpla lo anterior se lanza el evento DragDrop, en esete evento se extraen los datos del objeto DataObject y se displayan

 

Si vamos paso a paso

1.Detectando el Drag & Drop

Antes de que el DoDragDrop sea llamado, hay que detectar una operación de Drag con el ratón, esta operación suele ser los eventos MouseLeftButtonDown + un MouseMove, por lo que necesitamos subscribirnos a estos dos eventos

void Window1_Loaded(object sender, RoutedEventArgs e)
{
this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove);
} 

Para prevenir falsos drag & drop se pueden utilizar SystemParameters.MinimumHorizontalDragDistance  y SystemParameters.MinimumVerticalDragDistance, una vex que detectamos que no es un falso drag&drop guardamos la posición inicial

 

  void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
            {
                Point position = e.GetPosition(null);

                if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                  StartDrag(e); 

                }
            }   
        }

        void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _startPoint = e.GetPosition(null);
        }

2.Una vez detectado el drag

Necesitamos indicar los datos que queremos arrastrar para que el objeto al que se le va ha hacer el Drop luego sepa que datos le están llegado, estos datos se pasan a través del objeto DataObject a  través del método SetData que tiene dos parámetros el tipo de formato que se pasa y el objeto SetData (  Type format, object data ), el código del método  StartDrag quedaría de la siguiente forma

private void StartDrag(MouseEventArgs e)
        {
            IsDragging = true;
            DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
            IsDragging = false;
        }

En este caso no hemos definido ningún cursor diferente, si lo quisiéramos el código sería

private void StartDragCustomCursor(MouseEventArgs e)
        {

            GiveFeedbackEventHandler handler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
            this.DragSource.GiveFeedback += handler; 
            IsDragging = true;
            DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
            this.DragSource.GiveFeedback -= handler; 
            IsDragging = false;
        }

 

Como veis manejamos el evento GiveFeedback cuya implementación sería

 

void DragSource_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
                try
                {
                    //This loads the cursor from a stream .. 
                    if (_allOpsCursor == null)
                    {
                        using (Stream cursorStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
			"SimplestDragDrop.DDIcon.cur"))
                        {
                            _allOpsCursor = new Cursor(cursorStream);
                        } 
                    }
                    Mouse.SetCursor(_allOpsCursor);

                    e.UseDefaultCursors = false;
                    e.Handled = true;
                }
                finally { }
        }

 

3. Creando un efecto visual al arrastrar

 

Queda muy chulo que cuando arrastramos un control veamos difuminado el propio control que arrastramos, para ello utilizaremos un Adorner que ya explicamos, el constructor de este Adorner sería

 

public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
            : base(owner)
        {
            _owner = owner;
            if (useVisualBrush)
            {
                VisualBrush _brush = new VisualBrush(adornElement);
                _brush.Opacity = opacity;
                Rectangle r = new Rectangle();
                r.RadiusX = 3;
                r.RadiusY = 3;
                r.Width = adornElement.DesiredSize.Width;
                r.Height = adornElement.DesiredSize.Height;

                XCenter = adornElement.DesiredSize.Width / 2;
                YCenter = adornElement.DesiredSize.Height / 2;

                r.Fill = _brush;
                _child = r;

            }
            else
                _child = adornElement;

        }

 

Si os  fijáis se utiliza un VisualBrush para reflejar el contenido del elemento que arrastramos, si queremos utilizarlo el metodo StartDrag, cambia un “poquito”

 

private void StartDragInProcAdorner(MouseEventArgs e)
        {

            
            DragScope = Application.Current.MainWindow.Content as FrameworkElement;
            System.Diagnostics.Debug.Assert(DragScope != null);

                        bool previousDrop = DragScope.AllowDrop;
            DragScope.AllowDrop = true;            

          

            GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
            this.DragSource.GiveFeedback += feedbackhandler;

           
            DragEventHandler draghandler = new DragEventHandler(Window1_DragOver);
            DragScope.PreviewDragOver += draghandler; 

           
            DragEventHandler dragleavehandler = new DragEventHandler(DragScope_DragLeave);
            DragScope.DragLeave += dragleavehandler; 

                       QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragScope_QueryContinueDrag);
            DragScope.QueryContinueDrag += queryhandler; 

          
            _adorner = new DragAdorner(DragScope, (UIElement)this.dragElement, true, 0.5);
            _layer = AdornerLayer.GetAdornerLayer(DragScope as Visual);
            _layer.Add(_adorner);


            IsDragging = true;
            _dragHasLeftScope = false; 
                       DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);

                       DragScope.AllowDrop = previousDrop;
            AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
            _adorner = null;

            DragSource.GiveFeedback -= feedbackhandler;
            DragScope.DragLeave -= dragleavehandler;
            DragScope.QueryContinueDrag -= queryhandler;
            DragScope.PreviewDragOver -= draghandler;  

            IsDragging = false;
        }

4. Control destino

En el control destino además de haber habilitado la propiedad AllowDrop a True  podemos subscribirnos a los eventos

  • DragEnter:
  • DragLeave
  • DragOver

Estos eventos se suelen utilizar para cambiar los efectos o Adorner y dar un feedback al usuario

El evento mas importante es el Drop que se disparara cuando el usuario suelte el botón del ratón, aquí deberemos de hacer las acciones necesarias.

void Window1_Loaded(object sender, RoutedEventArgs e)
     {
         this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
         this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove); 
         // DROP 
         this.DropTargetImages.DragEnter += new DragEventHandler(DropTargetImages_DragEnter);
         this.DropTargetImages.DragLeave += new DragEventHandler(DropTargetImages_DragLeave);
         this.DropTargetImages.DragOver += new DragEventHandler(DropTargetImages_DragOver);
         this.DropTargetText.DragOver += new DragEventHandler(DropTargetText_DragOver);
         this.DropTargetText.Drop += new DragEventHandler(DropTargetText_Drop); 
     } 
     void DropTargetText_Drop(object sender, DragEventArgs e)
     {
         IDataObject data = e.Data; 
         if (data.GetDataPresent(DataFormats.Text))
         {
             MessageBox.Show( 
                 string.Format("formato correcto,  '{0}'" , 
                 ((string)data.GetData(DataFormats.Text)))); 
         } 
     } 
     void DropTargetImages_DragOver(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Images"))
         {
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         }
     } 
     void DropTargetText_DragOver(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Text"))
         { 
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         } 
     } 
     void DropTargetImages_DragLeave(object sender, DragEventArgs e)
     {
         RunStoryboard("Timeline2"); 
     } 
     void RunStoryboard(string name)
     {
         Storyboard sb = this.FindResource(name) as Storyboard ;
         System.Diagnostics.Debug.Assert(sb != null);
         sb.Begin(DropTargetImages); 
     } 
     void DropTargetImages_DragEnter(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Image"))
         {
             RunStoryboard("Timeline1"); 
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         } 
     }

Aquí tenéis el código

Tip – DataContext Null

En los foros de MSDN se preguntaba que pasaba con el Binding cuando al inicializarse una ventana el DataContext es nulo y el ejemplo que ponía era el siguiente, tenía el siguiente estilo

 

<Style x:Key="SelfCollapsingTextBlock"  TargetType="{x:Type TextBlock}">    
        <Style.Triggers>
            <Trigger Property="IsEnabled" Value="false">                
                <Setter Property="Visibility" Value="Collapsed"/>
            </Trigger>
        </Style.Triggers>
    </Style>

 

El textBlock

<Border Name="border">
<TextBlock Style="{StaticResource SelfCollapsingTextBlock}"  IsEnabled="{Binding IsFilterEnabled}"   Text="(filtered)" />
</Border>

Según el, como el DataContext era nulo al inicializarse la ventana IsÉnabled debería estar a False y por consecuencia aplicando el estilo el TextBlock debería collapsarse y esto no ocurría.

 

La razón es simple si el DataContext es null , el Binding resetea la Dependency Property a su valor por defecto y en este caso el valor por defecto de IsEnabled es True con lo que  no se podía colapsar el TextBlock

Adorner en WPF

Los Adorner en WPF son simple decoradores de UIElements, esta clase Adorner es un FrameWorkElement que se “enlaza” al control de usuario que se quiere adornar, realmente es un adorno visual, al cual se puede añadir funcionalidad. Para que se entienda mejor en Office 2007 podría ser un Adorner la mini Toolbar que aparece cuando seleccionamos una palabra

WordMiniToolbar

Un Adorner nos va a permitir Añadir visual feedback a un control, o notificaciones al usuario o… lo que diseñéis.

Vamos a  construir un ejemplo para que se vea mejor, este ejemplo es en un TextBox vamos ha hacer que aparezca por encima un botón con la imagen de Internet Explorer, este botón aparecerá si el texto comienza por “http://”, no lo hare con expresiones regulares ya que no es el objetivo.

Comencemos, creamos una clase que llamaremos TextBoxAdorner, esta clase si queremos que sea un Adorner deberá heredar de la clase abstracta Adorner que nos obliga a crear un constructor con parámetros de esta manera

 

 public TextBoxAdorner(UIElement adornerelement)
           : base(adornerelement)

Como mínimo tiene que tener un parámetro que es el objeto (UIElement) que vamos a adornar, podrá ser un constructor con los parámetros que queráis pero como mínimo tendrá esta firma.

Dentro del constructor almacenamos el elemento a adornar y lo añadimos al árbol visual del Adorner en este caso el boton

 

VisualCollection visualChildren;
        Button btnInternet = new Button();

        public TextBoxAdorner(UIElement adornerelement)
            : base(adornerelement)
        {
            visualChildren = new VisualCollection(this);

            CreateButton();
        }

        private void CreateButton()
        {
            btnInternet.Height = 20;
            btnInternet.Width = 20;
            btnInternet.Background = new ImageBrush(new BitmapImage(
                    new Uri(@"imgExplorer.jpg", UriKind.Relative)));
            btnInternet.Click += new RoutedEventHandler(btnInternet_Click);
            visualChildren.Add(btnInternet);
        }

Si queremos que el elemento pasado en AddVisualChild sea visible debemos sobrescribir VisualChildrenCount y GetVisualChild() para

devolver el UIElement que se ha añadido al árbol visual

  protected override int VisualChildrenCount { get { return visualChildren.Count; } }
        protected override Visual GetVisualChild(int index) { return visualChildren[index]; }

 

El siguiente paso es sobrescribir ArrangeOverride para dibujar nuestro decorador en el elemento a adornar

 

 protected override Size ArrangeOverride(Size finalSize)
        {
            // desiredWidth and desiredHeight are the width and height of the element that's being adorned.
            double desiredWidth = AdornedElement.DesiredSize.Width;
            double desiredHeight = AdornedElement.DesiredSize.Height;

            double adornerWidth = this.DesiredSize.Width;
            double adornerHeight = this.DesiredSize.Height;
            btnInternet.Arrange(new Rect(adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));


            return finalSize;
        }

En este evento indicamos donde dibujamos el adorner

Para utilizar el Adorner que hemos creado es tan sencillo como

private Adorner addSymbolAdorner;

        public Window1()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Window1_Loaded);
        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            addSymbolAdorner = new TextBoxAdorner(TestTextBox);
            AdornerLayer.GetAdornerLayer(TestTextBox).Add(addSymbolAdorner);
        }

El Adorner que hemos creado es muy sencillo, tenéis aquí la aplicación completa para que podáis verla con tranquilidad

 

Tenéis muchos mas ejemplos de Adorner, uno muy interesante es de  aquí de Denis Vuyka, también están los ejemplos de del SDK

Mono para el IPhone

Alucinado me ha dejado esta noticia, el equipo de Miguel de Icaza esta desarrollando el producto MonoTouch que según dicen están pensando lanzarlo la primera semana de Septiembre.

Después de haber jugueteado con Objective-C (con muchos dolores de cabeza) si es cierta esta noticia los desarrolladores de .NET estaremos de enhorabuena, seguiré esta noticia con atención a ver si se cumple, y además están buscando desarrolladores para probarlo

 

http://spreadsheets.google.com/viewform?hl=en&formkey=dHRXeFI5b1NjUWdRRkpiSmxkanh6T1E6MA..

IDataErrorInfo , Databinding y WPF una combinación perfecta

Cuando desarrollamos aplicaciones empresariales es importante marcar los errores a los usuarios según van introduciendo los datos, ya vimos algo de esto en el post Validation Rules en WPF donde definamos reglas de validación en la aplicación de WPF. A mi no es que me guste mucho porque si trabajamos con aplicaciones en capas, yo suelo poner este tipo de validaciones en la propia entidad ya que si es utilizada en diferentes pantallas o controles no tengo que replicar este control.

Si imaginamos la aplicación anterior, recordamos que teníamos un campo de entrada del rating que va de 1 a 5, peor no lo controlábamos. Vamos a controlar este error aplicando esta interfaz.

image

 

Para ello lo primero es que la clase Person implemente la interfaz IDataErrorInfo, esta interfaz nos obliga a implementar dos metodos

 

 public string this[string columnName]
        {
            get { throw new NotImplementedException(); }
        }

        public string Error
        {
            get { throw new NotImplementedException(); }
        }

 

En el primer método controlaremos  cada columna  y devolveremos el string con el error, en nuestro caso

 

public string this[string columnName]
        {
            get
            {
                string result = string.Empty;
                if (columnName == "ImageRating")
                {
                    if (ImageRating < 1 || ImageRating > 5)
                        result = "El rating debe de estar entre 1 y 5";
                }
                return result;
            }
        }

Ahora modificaríamos el binding del textbox de rating

      <TextBox  Height="30"    FontSize="20" Foreground="Blue" Text="{Binding Path=ImageRating }" HorizontalAlignment="Left" >

                    </TextBox>

Por el binding para que se produzca la validación del objeto cuando pierda el foco

  <TextBox  Height="30"    FontSize="20" Foreground="Blue"  HorizontalAlignment="Left" >
                        <Binding UpdateSourceTrigger="LostFocus" 
                     Path="ImageRating" ValidatesOnDataErrors="True"   />

                    </TextBox>

De esta manera se lanzara la validación en el objeto cuando el campo pierde el foco, como se muestra esto en la ventana. Si no ponemos nada como en este caso, el campo se pone rojo automáticamente de esta manera

image

Ahora es cuestión nuestra el mostrar el mensaje del error como queramos, un ejemplo sería lanzar el tooltip. Para ello definimos el estilo siguiente

<Style TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                Value="{Binding RelativeSource={RelativeSource Self}, 
                       Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>

image

Antes de terminar este post me gustaría que os fijaseis en el XAML del estilo, en este caso no tiene una key que identifique el estilo, esto va ha hacer que se aplique este estilo a todos los textbox que no tengan un estilo asignado, de esta manera podemos hacer estilos por defectos a los controles.

Si nos fijamos en el binding la propiedad UpdateSourceTrigger vemos que tiene la propiedad LostFocus. UpdateSourceTrigger definirá de que manera los datos serán modificados en el objeto origen, para que esta propiedad tenga sentido es claro que el Mode deberá ser TwoWay o OneWayToSource, ya que son los casos que admiten la modificación de objeto origen.

Las opciones de UpdateSourceTrigger son:

  • “Cuando el elemento pierde el foco” (LostFocus)
  • “Cuando la propiedad cambia” (PropertyChanged)
  • “Especificado vía código” (Explicit)
  • “Valor definido por defecto en la dependency property” (Default

Si se utiliza la opción “Explicit” por ejemplo en el TexBox DNI de la siguiente forma:

<TextBox Margin="3" x:Name="txtNombre" Text="{Binding Nombre, UpdateSourceTrigger=Explicit}" />

Para lanzar el trigguer desde código

txtNombre.GetBindingExpression(TextBox.TextProperty).UpdateSource();