Se afișează postările cu eticheta wpf. Afișați toate postările
Se afișează postările cu eticheta wpf. Afișați toate postările

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.

[C#] Cum să redai un fișier audio WAV incorporat într-o resursă?

Metoda prezentată în acest articol este funcțională și în proiecte WinForms dar și în cele WPF.

// la începutul fișierului de cod-din-spate
// trebuie pus acest rând dacă nu există deja:
using System.Media;

Putem folosi metoda Load sau LoadAsync pentru a încărca fișierul wav înainte de redarea lui și intercepta evenimentul LoadCompleted pentru a acționa după încărcarea fișierului audio (din testele mele, acest eveniment este apelat doar când se oferă un Stream, nu un nume de fișier, și nu este apelat când încărcarea dă eroare), dar metodele Play, PlaySync și PlayLooping fac asta automat înainte de redare.

Dorim ca fișierul audio să fie preluat din dosarul executabilului

Adăugăm fișierul WAV la proiect prin meniul Project > Add Existing Item... apoi îl selectăm în Solution Explorer și în panoul Properties atribuim proprietății cu numele Copy to Output Directory valoarea Copy if newer.

// în metoda handler la clic pe un buton se pun următoarele rânduri
SoundPlayer p = new SoundPlayer("nume_fisier.wav");
p.PlayLooping(); // repetă la infinit sunetul; alternativ: p.Play() care nu redă repetat sau p.PlaySync() care blochează firul de execuție până la finalizarea redării

În cazul încărcării asincrone prin LoadAsync (care ajută la fișiere mari, dar am adăugat un fișier WAV de aprox 700MB la proiect și s-a blocat, la repornire nu era fișierul în proiect), poate fi folositor să declarăm variabila p (de tip SoundPlayer) la nivelul clasei (de ex. Form1).

Sau dorim fișierul audio incorporat în fișierul executabil

Adăugăm fișierul WAV la proiect prin meniul Project > Add Existing Item... apoi îl selectăm în Solution Explorer și în panoul Properties atribuim proprietății cu numele Build Action (ro. Acțiunea la construire) valoarea Embedded Resource (ro. Resursă incorporată). Proprietatea cu numele Copy to Output Directory nu mai are relevanță în acest exemplu.

Dacă setăm Copy to Output Directory la valoarea Do not copy, la următoarea compilare (construcție sau en. build), fișierul audio nu va mai fi în directorul bin/Debug (unde se află executabilul generat de obicei), dar datorită proprietății Build Action va fi conținut de executabil.

În exemplul acesta numele fișierului audio este chimes.wav, fișier preluat din link-ul de mai jos. În fereastra de proprietăți ale proiectului (clic-dreapta pe proiect în panoul Solution Explorer, apoi în meniul apărut se selectează Properties) se caută în secțiunea Application setarea „Default namespace” (spațiul de nume implicit al proiectului). În cazul exemplului, aceasta are valoarea „cs_soundplayer_test”.

System.Reflection.Assembly a =
    System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream s =

    a.GetManifestResourceStream("cs_soundplayer_test.chimes.wav");
SoundPlayer p = new SoundPlayer(s); // nu c-torul încarcă fluxul WAV în memorie ci apelul de mai jos:
p.PlayLooping();

Capturi de ecran








Secvențele de cod din acest articol au fost testate pe Windows 10 cu .NET Framework 4.6.1.

Inspirație [EN]:
Sunetele implicite din Windows 7 pot fi descărcate de aici: https://winsounds.com/windows-7-default-sounds/.

Documentație oficială [EN]: https://msdn.microsoft.com/en-us/library/system.media.soundplayer(v=vs.110).aspx.