Despre parametrii primiți de gestionarii de evenimente rutate (RoutedEventArgs, MouseButtonEventArgs etc.)

Exemplu:

private void Handler(object sender, MouseButtonEventArgs e)
{
    // ce înseamnă aici: sender, e.Source și e.OriginalSource ?
}
  • sender - elementul care gestionează evenimentul (de care gestionarul de evenimente este atașat)
  • MouseButtonEventArgs moștenește de la RoutedEventArgs:
    • e.Source - elementul pentru care a pornit evenimentul în arborele vizual (fie de sus în jos, fie de jos în sus)
    • e.OriginalSource - elementul cel mai adânc determinat prin hit testing din interiorul celui pentru care a pornit evenimentul în arborele vizual

Dat fiind arborele vizual al sursei XAML următoare:

<StackPanel x:Name="sp" UIElement.PreviewMouseLeftButtonDown="Handler">
    <Button x:Name="btn1">butonul 1</Button>
    <Button x:Name="btn2">butonul 2</Button>
    <Button x:Name="btn3" Padding="5">
        <Rectangle Width="100" Height="100" Fill="Blue" x:Name="r"/>
    </Button>
</StackPanel>

și în codul din spate:

private void Handler(object sender, MouseButtonEventArgs e)
{
    // ce înseamnă aici: sender, e.Source și e.OriginalSource ?
}

Când se face clic cu butonul de maus stâng pe:

  • btn1: sender va fi sp, e.Source va fi btn1, e.OriginalSource va fi btn1
  • btn2: sender va fi sp, e.Source va fi btn2, e.OriginalSource va fi btn2
  • btn3: sender va fi sp, e.Source va fi btn3, e.OriginalSource va fi:
    • r dacă se face clic pe pătratul albastru
    • btn3 dacă se face clic pe spațiul din btn3 din jurul lui r

Am folosit evenimentul PreviewMouseLeftButtonDown în loc de Click deoarece Click este un eveniment specific butoanelor, și când se face clic pe pătratul albastru din btn3, e.OriginalSource este tot btn3 în cazul evenimentului Click. Nu am folosit nici evenimentul MouseLeftButtonDown deoarece este gestionat de clasa Button și nu ajunge la Rectangle. Notația cu UIElement. funcționează deoarece Button și Rectangle, ambele moștenesc de la UIElement și XAML oferă posibilitatea de a atașa un gestionar de evenimente la strămoșul controalelor vizate de respectivul eveniment cu această notație.

Documentația oficială este aici. Alte informații relevante aici.

Cum să depanezi probleme de legătură a datelor în WPF

Tradus din engleză de aici: https://spin.atomicobject.com/2013/12/11/wpf-data-binding-debug/.


Legătura datelor stabilește o conexiune între interfața grafică a aplicației și logica afacerii. Când funcționează, este un lucru minunat. Nu mai trebuie să scrii cod care actualizează interfața ta grafică sau să pasezi valori în jos către logica afacerii. Când nu mai funcționează, poate fi frustrant să afli ce a mers prost. În această postare, îți voi da câteva sfaturi despre cum poți depana legăturile tale de date în WPF.

1. Adaugă urmărirea în fereastra Output

Aici este un simplu TextBlock care are un context de date lipsă. În această situație, nu vei obține nicio eroare în fereastra de ieșire a Visual Studio.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Text="{Binding ThereIsNoDataContext}"/>
    </Grid>
</Window>

Pentru a activa urmărirea, am adăugat un nou spațiu de nume XML pentru a include spațiul de nume System.Diagnostics. Poți de asemenea seta nivelul de urmărire la High, Medium, Low sau None. Acum haide să adăugăm ceva urmărire la fereastra de ieșire pentru a vedea ce este greșit cu legătura datelor.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Text="{Binding ThereIsNoDataContext, 
            diag:PresentationTraceSources.TraceLevel=High}"/>
    </Grid>
</Window>

Acum fereastra de ieșire va conține următoarele informații ajutătoare:

System.Windows.Data Warning: 71 : BindingExpression (hash=38600745): DataContext is null

2. Atașează un convertor de valoare pentru a rupe execuția intrând în depanator

Când nu vezi nimic afișat în interfața ta grafică, este greu să spui dacă legătura datelor este cea care cauzează problema ta sau este o problemă cu așezarea vizuală a controlului. Poți elimina legătura datelor ca problema în cauză prin adăugarea unui convertor de valoare și să rupi execuția intrând în depanator. Dacă valoarea este cea așteptată, atunci legătura datelor nu este o problemă a ta.

Iată un simplu convertor de valoare care rupe execuția intrând în depanator.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Data;
 
namespace WpfApplication1
{
    public class DebugDataBindingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            Debugger.Break();
            return value;
        }
 
        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            Debugger.Break();
            return value;
        }
    }
}


Pentru a folosi convertorul de valoare, faceți referire la spațiul de nume al asamblajului care conține convertorul și adaugă o instanță a lui la resursele ferestrei tale. Acum adaugă convertorul la legătura de date problematică a ta.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid Name="root">
        <Grid.Resources>
            <local:DebugDataBindingConverter x:Key="DebugBinding"/>
        </Grid.Resources>
        <TextBlock Text="{Binding ActualWidth, ElementName=root, 
            Converter={StaticResource DebugBinding}}"/>
    </Grid>
</Window>

3. Află instantaneu că ai o problemă de legătură a datelor

Decât dacă verifici constant fiecare element de interfață cu utilizatorul și monitorizezi fereastra de ieșire pentru erori de legătură a datelor, nu vei ști întotdeauna când ai o problemă de legătură a datelor. O excepție nu este aruncată când legătura datelor se strică, deci gestionarii de excepții globali nu sunt folositori.

Nu ar fi frumos dacă ai intra în depanator exact când ai o eroare de legătură a datelor? Adăugând propria noastră implementare a unui TraceListener care intră în depanator, vom fi notificați data viitoare când primim o eroare de legătură a datelor. Am adăugat de asemenea implicitul ConsoleTraceListener pe lângă noul nostru DebugTraceListener, astfel încât exemplele noastre anterioare de urmărire a ieșirii să nu fie stricate.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        PresentationTraceSources.Refresh();
        PresentationTraceSources.DataBindingSource.Listeners.Add(new ConsoleTraceListener());
        PresentationTraceSources.DataBindingSource.Listeners.Add(new DebugTraceListener());
        PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Warning | SourceLevels.Error;
        base.OnStartup(e);
    }
}
 
public class DebugTraceListener : TraceListener
{
    public override void Write(string message)
    {
    }
 
    public override void WriteLine(string message)
    {
        Debugger.Break();
    }
}

Cu aceste sfaturi, ar trebui să ai o experiență mai pozitivă cu legăturile datelor. Te rog să împărtășești în comentarii orice sfat de depanare în plus pe care l-ai putea avea.

Ce să faci când FrameworkElement.GetTemplateChild(string) întoarce null?



Dacă metoda GetTemplateChild aparent nu funcționează, încearcă să faci un constructor static pentru clasa elementului asupra căruia se aplică respectivul ControlTemplate:

De exemplu, dacă clasa se numește AudioFileSelector, acest cod va face ca GetTemplateChild să nu mai întoarcă null. mereu.



static AudioFileSelector()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(AudioFileSelector),
        new FrameworkPropertyMetadata(typeof(AudioFileSelector)));
}

Apoi, aproape la fel ca atunci când ai un UserControl cu elemente în conținutul lui cu atributul Name sau x:Name setat și le folosești direct specificându-le numele în fișierul cu extensia .xaml.cs, acum trebuie să declari la nivelul clasei, de obicei în același fișier .xaml.cs, câteva câmpuri (field-uri), câte unul pentru fiecare element cu nume din interiorul Template-ului pe care dorești să le folosești, chiar și doar pentru a gestiona anumite evenimente ale lor:

internal Button MyButton1, MyTextBox1;

și pui o suprascriere (override) la metoda OnApplyTemplate asftel:

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
   
    MyButton1 = (Button)GetTemplateChild("MyButton1");
    MyTextBox1 = (TextBox)GetTemplateChild("MyTextBox1");

    // eventual se atașează aici câteva handlere la evenimente ale MyButton1, MyTextBox1 etc.
}

apoi în interiorul clasei, oriunde este nevoie de aceste elemente ale ControlTemplate-ului se apelează:

ApplyTemplate();

în cazul în care acest apel întoarce true, se pot imediat apoi folosi câmpurile MyButton1, MyTextBox1 etc.

Apelul la ApplyTemplate apelează indirect OnApplyTemplate. El este necesar mai ales când este nevoie de un subelement înainte ca evenimentul Loaded al elementului asupra căruia se aplică ControlTemplate-ul să fie apelat.