Recibiendo notificación de cuándo hacemos clic fuera del elemento que tiene el ratón

Suena rarísimo, pero si quieres que cuando el elemento que ha capturado el ratón reciba una notificación de cuándo el usuario pulsa fuera del mismo, aquí tienes un buen post de las locuras que te esperan:

Muhammad Shujaat Siddiqi

Exploring the world of software engineering…

http://www.shujaat.net/2011/04/clicking-away-from-framework-element.html

This post is about a very specific problem about mouse capturing. There are certain situations in which we need to be notified when mouse is clicked outside the bounds of an element. As all of you know that this is classical mouse capturing situation used for various tasks including Drag & Drop. When we search around for a solution for this then we find out an attached event Mouse.PreviewMouseDownOutsideCapturedElement. This is documented in msdn as follows:

“Occurs when the primary mouse button is pressed outside the element that is capturing mouse events.”

<Window x:Class="WpfApplication3.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="Window1" Height="483" Width="553"      

Mouse.PreviewMouseDownOutsideCapturedElement="Window_PreviewMouseDownOutsideCapturedElement" >

    <StackPanel>

        <StackPanel Height="33">

            <TextBox Name="textBox1" />

        </StackPanel>

        <StackPanel  >           

            <Button Content="Button" Height="32" HorizontalAlignment="Left"

                    Margin="126,209,0,0" Name="button1" VerticalAlignment="Top"

                    Width="297" Click="button1_Click" />

        </StackPanel>

    </StackPanel>

</Window>

This definitely does what it is supposed to do. In order to get this to work we must have a captured element. Let’s capture mouse on textBox1. So, clicking a mouse outside textBox1 (captured element) should result in causing this handler to get called.

public partial class Window1 : Window

{

    public Window1()

    {

        InitializeComponent();       

    }

 

    private void Window_PreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e)

    {

 

    }

 

    private void button1_Click(object sender, RoutedEventArgs e)

    {

        Mouse.Capture(this.textBox1);

    }

}

 

Let’s run this. We have the display as presented in XAML. It has a TextBox and a Button. When we click the button textBox1 captures the mouse using static Capture method on Mouse. We have subscribed PreviewMouseDownOutsideCapturedElement event in XAML. We could have easily done that in code behind as well. Obviously we would need AddHandler… mechanism for registering with an attached event. This is similar to XAML attached properties. When implemented in WPF, they work as Dependency properties. Similarly Attached events from XAML are implemented as routed events in WPF.

Now the issue is that this event gets fired even when we click inside textBox1. This seems awkwardly strange as this is purely not expected behavior when we read the msdn description. Now we know the behavior. How can we fix this? We just need to find out where the mouse was clicked and we should be good to go. You might be wondering we can get around that we can do that either through the sender parameter or Source / OriginalSource from MouseButtonEventArgs. But the strange thing is that sender is always the element on which the event is registered. So if were registering it on textBox1, it would be textBox1. Currently this would always be the Window object no matter where we click on the Window after capturing the mouse by clicking the button. On top of that, Source and OriginalSource are always the captured element. The other properties which could inform us if the mouse is directly over the textBox1 are always true.

The only way to fix it seems to be to do it ourselves. We can find out the position of mouse. If the position of mouse is within the bounds of captured element then we can just ignore this. Otherwise, we can execute the same logic as we were supposed to execute. We might need to register the event again.

 

private void Window_PreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e)

{

    bool isClickedWithin =  IsMouseClickWithin(this.textBox1, e.MouseDevice.GetPosition(this.textBox1));

 

    if(isClickedWithin)

    {

        //execute some logic

    }

}

 

private bool IsMouseClickWithin(FrameworkElement element, Point point)

{

    return (element.ActualWidth > point.X && element.ActualHeight > point.Y) || point.X < 0 || point.Y < 0;

}

 

In the above example, it is checking if the mouse position is within the actual bounds of textBox1. IsMouseClickWithin returns true for this. It returns false otherwise.

En definitiva, más cutre imposible, pero dice que funciona. Vamos a probarlo.

Reflection, coger propiedades

return GetAllProperties(type, 
	BindingFlags.Public | 
	BindingFlags.Instance | 
	BindingFlags.SetProperty | 
	BindingFlags.DeclaredOnly, 
	typeof(GraphicsBase)).ToList();

Solamente con esto, ya tienes canela fina. Aunque has de ver que el GetAllProperties es un método que me he sacado del mangote para coger incluso las propiedades de la jerarquía de clases, parando si se encuentra el tipo indicado en el argumento, en el caso que se indique.

private static IEnumerable<PropertyInfo> GetAllProperties(Type currentType, BindingFlags flags, Type ancestorType = null)
{
    var properties = new List<PropertyInfo>(currentType.GetProperties(flags));
    if (currentType.BaseType != null && ancestorType != currentType)
        properties.AddRange(GetAllProperties(currentType.BaseType, flags, ancestorType));
    return properties;
}

PropertyItem, una triquiñuela para ordenar nombre de campo/valor

Ahí lo llevas, más cómodo que otra cosa.

Si queremos organizar una lista de propiedades y que las etiquetas queden alineadas con un Grid.IsSharedSize, usa el majestuoso PropertyItem, que aunque sea como versa el título de esta entrada, puede ser realmente útil.

public class PropertyItem : ItemsControl
    {
        static PropertyItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyItem), new FrameworkPropertyMetadata(typeof(PropertyItem)));
        }

        public PropertyItem()
        {
            IsVisibleChanged += (sender, args) =&gt; SetColumns();
            Loaded += (sender, args) =&gt; SetColumns();
        }

        private void SetColumns()
        {

            var column = 0;

            if (Items.Count == 1)
            {
                var container = ContainerFromElement(this, (DependencyObject) Items[0]);
                if (container != null)
                {
                    container.SetValue(Grid.ColumnSpanProperty, 2);
                }
            }
            else
            {
                foreach (DependencyObject item in Items)
                {
                    var container = ContainerFromElement(this, item);
                    if (container != null)
                    {
                        container.SetValue(Grid.ColumnProperty, column);
                    }

                    column = 1;
                }
            }
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new ContentControl();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is ContentControl;
        }
    }

Traducciones en la UI on the rocks

// Establecle que todos los elementos de la interfaz de designer tendrán la propiedad Language establecida al idioma de CurrentCulture)

LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));

Reafirma tu interfaz y nutre sus decimales con comitas en español al mostrar numeretes decimales! 0% de materia grasa.

Cuando arrivo a casa, un GroupToolBar delichioso

Estamos hartos de la jodida barra de herramientas que tantos disgustos nos ha dado. Es un infierno de estilo lo que hay que aplicarle para que la cosa fufe alright, pero finalmente puede valer la pena. Esta vez, la perfectiva es distinta. Una barra de herramientas como las de toda la vida, pero que lleva integrada una ListBox con toda la parafernalia y además, un GroupStyle que lo flipes, para que cuando agrupes herramientillas, la cosa quede recogida finamente y todo el mundo se sienta orgulloso de esos grupos de moda.

Bueno, voy a dejar las drogas.

Digo que he estado dándolo todo el fin de semana para que la barrita de taredas sea más golfa que nunca y la verdad es que no ha quedado mal. Cuando haga las pruebas correspondientes y vea que es incluso más apropiada que la anterior, tendremos nueva subida en CodePlex. Mientras tanto un poco de humor vía CoderFacts: