« WPF » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(7 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 225 : | Ligne 225 : | ||
::::TextBlock | ::::TextBlock | ||
== | == Get children of a specific type == | ||
<kode lang=csharp> | <kode lang=csharp> | ||
public static IEnumerable<T> FindChildren<T>(this DependencyObject parent, Func<T, bool> predicate) | public static IEnumerable<T> FindChildren<T>(this DependencyObject parent, Func<T, bool> predicate) | ||
Ligne 265 : | Ligne 265 : | ||
} | } | ||
</kode> | </kode> | ||
== Get parent of a specific type == | |||
<kode lang='cs'> | |||
public static T GetParent<T>(DependencyObject child) where T : DependencyObject | |||
{ | |||
var parentObject = VisualTreeHelper.GetParent(child); | |||
if (parentObject == null) | |||
{ | |||
return null; | |||
} | |||
if (parentObject is T matchingParent) | |||
{ | |||
return matchingParent; | |||
} | |||
return GetParent<T>(parentObject); | |||
} | |||
</kode> | |||
== Listen to the changes of a property == | |||
<filebox fn='MyViewModel.cs'> | |||
internal sealed class MyViewModel : BindableBase | |||
{ | |||
private readonly IDisposable myPropertySubscription; | |||
private string myProperty; | |||
public MyViewModel(MyViewContext context) | |||
{ | |||
this.context = context; | |||
this.myPropertySubscription = this.context.AsWeakObservableOfPropertyChanged(x => x.MyProperty).Subscribe(this.OnMyPropertyChanged); | |||
this.RefreshFromContext(); | |||
} | |||
public string MyProperty | |||
{ | |||
get => this.myProperty; | |||
set => this.SetProperty(ref this.myProperty, value); | |||
} | |||
protected override void Dispose(bool disposing) | |||
{ | |||
base.Dispose(disposing); | |||
this.myPropertySubscription.Dispose(); | |||
} | |||
private void OnMyPropertyChanged(string myProp) => this.RefreshFromContext(); | |||
private void RefreshFromContext() | |||
{ | |||
this.MyProperty = this.context.MyProperty; | |||
} | |||
} | |||
</filebox> | |||
<filebox fn='NotifyPropertyChangedExtensions.cs' collapsed> | |||
public static class NotifyPropertyChangedExtensions | |||
{ | |||
public static IObservable<TProperty> AsObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) | |||
where T : INotifyPropertyChanged | |||
{ | |||
if (propertyExpression == null) | |||
{ | |||
throw new ArgumentNullException(nameof(propertyExpression)); | |||
} | |||
var propertyName = ((MemberExpression)propertyExpression.Body).Member.Name; | |||
var propertyGetter = propertyExpression.Compile(); | |||
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( | |||
handler => handler.Invoke, | |||
handler => source.PropertyChanged += handler, | |||
handler => source.PropertyChanged -= handler) | |||
.Where(e => e.EventArgs.PropertyName == propertyName) | |||
.Select(e => propertyGetter.Invoke(source)); | |||
} | |||
public static IObservable<TProperty> AsWeakObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) | |||
where T : INotifyPropertyChanged | |||
{ | |||
return GetWeakObservableOfPropertyChanged(source, propertyExpression, false); | |||
} | |||
public static IObservable<TProperty> AsWeakObservableOfPropertyChangedAndCurrentValue<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) | |||
where T : INotifyPropertyChanged | |||
{ | |||
return GetWeakObservableOfPropertyChanged(source, propertyExpression, true); | |||
} | |||
private static IObservable<TProperty> GetWeakObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression, bool startWithCurrentValue) | |||
where T : INotifyPropertyChanged | |||
{ | |||
if (propertyExpression == null) | |||
{ | |||
throw new ArgumentNullException(nameof(propertyExpression)); | |||
} | |||
var propertyName = ((MemberExpression)propertyExpression.Body).Member.Name; | |||
var propertyGetter = propertyExpression.Compile(); | |||
var observer = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( | |||
handler => handler.Invoke, | |||
handler => PropertyChangedEventManager.AddHandler(source, new EventHandler<PropertyChangedEventArgs>(handler), propertyName), | |||
handler => PropertyChangedEventManager.RemoveHandler(source, new EventHandler<PropertyChangedEventArgs>(handler), propertyName)) | |||
.Where(e => e.EventArgs.PropertyName == propertyName) | |||
.Select(e => propertyGetter.Invoke(source)); | |||
if (startWithCurrentValue) | |||
{ | |||
observer = Observable.Return(propertyGetter.Invoke(source)).Concat(observer); | |||
} | |||
return observer; | |||
} | |||
} | |||
</filebox> | |||
= WPF vs UWP = | = WPF vs UWP = |
Dernière version du 18 octobre 2021 à 13:07
Généralités
Permet la création d'interfaces graphiques. Remplace Windows Forms.
- Affichage vectoriel, cela permet d'augmenter la taille des objets en fonction de la résolution de l'écran sans effet de pixellisation.
- Séparation entre le code de l'interface graphique (XAML) et le code des fonctionnalités (C#).
Historique
WPF Version | Release | .NET Version | Visual Studio Version | Major Features |
---|---|---|---|---|
3.0 | 2006-11 | 3.0 | Initial Release. | |
3.5 | 2007-11 | 3.5 | VS 2008 | Changes and improvements in: Application model, data binding, controls, documents, annotations, and 3-D UI elements. |
3.5 SP1 | 2008-08 | 3.5 SP1 | Native splash screen support, New WebBrowser control, DirectX pixel shader support. Faster startup time and improved performance for Bitmap effects. | |
4.0 | 2010-04 | 4.0 | VS 2010 | New controls: Calendar, DataGrid, and DatePicker. Multi-Touch and Manipulation |
4.5 | 2012-08 | 4.5 | VS 2012 | New Ribbon control, New INotifyDataErrorInfo interface |
4.5.1 | 2013-10 | 4.5.1 | VS 2013 | |
4.5.2 | 2014-05 | 4.5.2 | ||
4.6 | 2015-07 | 4.6 | VS 2015 | Transparent child window support HDPI and Touch improvements |
Assemblages nécessaires
- PresentationCore
- PresentationFramework
- System.Xaml
- WindowsBase
Assemblages additionnels
Nom du paquet NuGet | Assemblages installés |
---|---|
Expression.Blend.Sdk.WPF |
|
System.Windows.Interactivity.WPF |
|
Blend.Interactivity.WPF |
|
Visual et Logical Tree
- Visual Tree: rendu final
- Logical Tree: code XAML
Snoop
Permet d'afficher l'arborescence visuelle d'une fenêtre WPF.
Crtl + Shift → sélectionner un élément visuel
Récupérer les Templates des composants de base
// récupérer le template par défaut de button1 définit dans le XAML using (var xw = new XmlTextWriter(@"ButtonDefaultTemplate.xml", null)) { xw.Formatting = Formatting.Indented; System.Windows.Markup.XamlWriter.Save(button1.Template, xw); } |
Forcer la culture locale dans WPF
Par défaut, WPF utilise la culture US English.
FrameworkElement.LanguageProperty.OverrideMetadata( typeof(FrameworkElement), new FrameworkPropertyMetadata( XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); |
Erreurs
Impossible de créer le type inconnu ...
<MainControl xmlns:xxx="clr-namespace:Namespace"> <!-- Ajouter l'assembly car la définition du namespace n'est pas suffisante --> <MainControl xmlns:xxx="clr-namespace:Namespace;assembly=Assembly"> |
Astuces
Icônes / Glyphes
NuGet → FontAwesome.WPF
<Window xmlns:fa="http://schemas.fontawesome.io/icons/"> <fa:ImageAwesome Icon="Refresh" Spin="True" Height="16" Width="16" /> |
Clipboard
Clipboard.SetText("Hello"); |
Control à définir depuis le code-behind
<!-- Permet de définir le Control à afficher depuis le code-behind --> <ContentControl Content="{Binding Path=AutreControl}"/> |
UserControl internal
MyUserControl.xaml.cs |
internal partial class MyUserControl : UserControl { } |
MyUserControl.xaml |
<UserControl x:Class="MyNamespace.MyUserControl" x:ClassModifier="internal"> |
Un Enum dans du XAML
<MainControl xmlns:current="clr-namespace:CurrentNameSpace" Propriete="{x:Static Member=current:MaClasse+MonEnum.Element1}"> |
namespace CurrentNameSpace { public class MaClasse { // public ou internal au minimum internal enum MonEnum { Element1, Element2 } } } |
Image
<Image Source="C:\chemin\image.jpg" /> <!-- si la taille de l'image est réduite, utiliser DecodePixelWidth ou DecodePixelHeight pour mettre en cache l'image réduite et non l'originale, ceci afin d'économiser de la mémoire --> <Image Width="100"> <Image.Source> <BitmapImage DecodePixelWidth="100" UriSource="C:\chemin\image.jpg" /> </Image.Source> </Image> <!-- ajouter l'image au projet: Build action → Content Copy to Output Directory → Copy if newer L'image n'est pas embarqué dans l'assemblage --> <Image Source="dossier/image.jpg" /> <!-- depuis un autre assemblage --> <Image Source="/AssemblyName;component/dossier/image.jpg" /> |
Le format SVG n'est pas supporter nativement, il faut le convertir en XAML (avec Inkscape). |
Binding et web API
private ImageSource _imageBoxStream; public ImageSource ImageBoxStream { get { return _imageBoxStream; } set { Set(() => ImageBoxStream, ref _imageBoxStream, value, true); } } var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); using (var stream = await response.Content.ReadAsStreamAsync()) { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.StreamSource = stream; bitmapImage.EndInit(); ImageBoxStream = bitmapImage; } |
<Image Source="{Binding ImageBoxStream}" Stretch="None" /> |
Image base64
var base64string = "data:image/jpeg;base64,XXXX"; var match = Regex.Match(response, @"data.+,(?<base64>.+)"); if (match.Success) { byte[] binaryData = Convert.FromBase64String(match.Groups["base64"].Value); var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = new MemoryStream(binaryData); bitmapImage.EndInit(); ImageBoxStream = bitmapImage; } |
OriginalSource vs Source sur RoutedEventArgs
OriginalSource correspond à l'objet visuel alors que Source correspond à l'objet XAML.
<Button Content="Text" PreviewMouseDown="Button_PreviewMouseDown"/> |
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e) { var source = e.Source; // Button var originalSource = e.OriginalSource; // ButtonChrome } |
Arbre visuel correspondant :
- Button
- ButtonChrome
- ContentPresenter
- TextBlock
- ContentPresenter
- ButtonChrome
Get children of a specific type
public static IEnumerable<T> FindChildren<T>(this DependencyObject parent, Func<T, bool> predicate) where T : DependencyObject { var children = new List<DependencyObject>(); if ((parent is Visual) || (parent is Visual3D)) { var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int childIndex = 0; childIndex < visualChildrenCount; childIndex++) { children.Add(VisualTreeHelper.GetChild(parent, childIndex)); } } foreach (var logicalChild in LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>()) { if (!children.Contains(logicalChild)) { children.Add(logicalChild); } } foreach (var child in children) { var typedChild = child as T; if ((typedChild != null) && predicate.Invoke(typedChild)) { yield return typedChild; } foreach (var foundDescendant in FindChildren(child, predicate)) { yield return foundDescendant; } } yield break; } |
Get parent of a specific type
public static T GetParent<T>(DependencyObject child) where T : DependencyObject { var parentObject = VisualTreeHelper.GetParent(child); if (parentObject == null) { return null; } if (parentObject is T matchingParent) { return matchingParent; } return GetParent<T>(parentObject); } |
Listen to the changes of a property
MyViewModel.cs |
internal sealed class MyViewModel : BindableBase { private readonly IDisposable myPropertySubscription; private string myProperty; public MyViewModel(MyViewContext context) { this.context = context; this.myPropertySubscription = this.context.AsWeakObservableOfPropertyChanged(x => x.MyProperty).Subscribe(this.OnMyPropertyChanged); this.RefreshFromContext(); } public string MyProperty { get => this.myProperty; set => this.SetProperty(ref this.myProperty, value); } protected override void Dispose(bool disposing) { base.Dispose(disposing); this.myPropertySubscription.Dispose(); } private void OnMyPropertyChanged(string myProp) => this.RefreshFromContext(); private void RefreshFromContext() { this.MyProperty = this.context.MyProperty; } } |
NotifyPropertyChangedExtensions.cs |
public static class NotifyPropertyChangedExtensions { public static IObservable<TProperty> AsObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) where T : INotifyPropertyChanged { if (propertyExpression == null) { throw new ArgumentNullException(nameof(propertyExpression)); } var propertyName = ((MemberExpression)propertyExpression.Body).Member.Name; var propertyGetter = propertyExpression.Compile(); return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( handler => handler.Invoke, handler => source.PropertyChanged += handler, handler => source.PropertyChanged -= handler) .Where(e => e.EventArgs.PropertyName == propertyName) .Select(e => propertyGetter.Invoke(source)); } public static IObservable<TProperty> AsWeakObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) where T : INotifyPropertyChanged { return GetWeakObservableOfPropertyChanged(source, propertyExpression, false); } public static IObservable<TProperty> AsWeakObservableOfPropertyChangedAndCurrentValue<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression) where T : INotifyPropertyChanged { return GetWeakObservableOfPropertyChanged(source, propertyExpression, true); } private static IObservable<TProperty> GetWeakObservableOfPropertyChanged<T, TProperty>(this T source, Expression<Func<T, TProperty>> propertyExpression, bool startWithCurrentValue) where T : INotifyPropertyChanged { if (propertyExpression == null) { throw new ArgumentNullException(nameof(propertyExpression)); } var propertyName = ((MemberExpression)propertyExpression.Body).Member.Name; var propertyGetter = propertyExpression.Compile(); var observer = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( handler => handler.Invoke, handler => PropertyChangedEventManager.AddHandler(source, new EventHandler<PropertyChangedEventArgs>(handler), propertyName), handler => PropertyChangedEventManager.RemoveHandler(source, new EventHandler<PropertyChangedEventArgs>(handler), propertyName)) .Where(e => e.EventArgs.PropertyName == propertyName) .Select(e => propertyGetter.Invoke(source)); if (startWithCurrentValue) { observer = Observable.Return(propertyGetter.Invoke(source)).Concat(observer); } return observer; } } |
WPF vs UWP
WPF
- OS antérieur à Windows 10
- accès bas niveau au noyau et au matériel
- applications non-sanboxées, accès aux APIs système
- APIs graphique: radial brush, clipping mask
- Fenêtre transparente
- Accès plus fin à la bar de titre et au chrome des fenêtres
- intégration de WindowsForm
- chargement de code dynamique: plugins
- applications complexes à plusieurs fenêtres
UWP
- distribution via le store
- applications sanboxées
- code à destination de différents supports: smartphone, Xbox