|
|
Ligne 271 : |
Ligne 271 : |
| { | | { |
| var parentObject = VisualTreeHelper.GetParent(child); | | var parentObject = VisualTreeHelper.GetParent(child); |
|
| |
| if (parentObject == null) | | if (parentObject == null) |
| { | | { |
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#).
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 |
- Microsoft.Expression.Interactions v4.5 (MouseDragElementBehavior, interactivity:DataTrigger)
- System.Windows.Interactivity v4.5 (Behavior, InvokeCommandAction, interactivity:EventTrigger)
|
System.Windows.Interactivity.WPF |
- Microsoft.Expression.Interactions v4.0
- System.Windows.Interactivity v4.0
|
Blend.Interactivity.WPF |
- Microsoft.Expression.Interactions v3.5
- System.Windows.Interactivity v3.5
|
Visual et Logical Tree
- Visual Tree: rendu final
- Logical Tree: code XAML
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);
}
|
WPF Control Template Viewer
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 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
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