{"id":10151,"date":"2026-05-28T14:51:29","date_gmt":"2026-05-28T13:51:29","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=10151"},"modified":"2026-05-28T14:55:38","modified_gmt":"2026-05-28T13:55:38","slug":"devexpress-wpf-components","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/devexpress-wpf-components\/","title":{"rendered":"DevExpress WPF Components"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">ExtendedLookUpEditor.xaml<\/h2>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"xml\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">&lt;UserControl x:Class=\"Views.Controls.ExtendedLookUpEditor\"\n             xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n             xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n             xmlns:dxg=\"http:\/\/schemas.devexpress.com\/winfx\/2008\/xaml\/grid\"\n             x:Name=\"root\">\n\n    &lt;dxg:LookUpEdit x:Name=\"editor\"\n                    MinWidth=\"0\"\n                    AutoPopulateColumns=\"False\"\n                    ValueMember=\"{Binding ElementName=root, Path=ValueMember}\"\n                    DisplayMember=\"{Binding ElementName=root, Path=DisplayMember}\"\n                    ItemsSource=\"{Binding ElementName=root, Path=ItemsSource}\"\n                    EditValue=\"{Binding ElementName=root, Path=EditValue, Mode=TwoWay}\">\n        &lt;dxg:LookUpEdit.StyleSettings>\n            &lt;dxg:SearchLookUpEditStyleSettings AllowGrouping=\"False\"\/>\n        &lt;\/dxg:LookUpEdit.StyleSettings>\n        &lt;dxg:LookUpEdit.PopupContentTemplate>\n            &lt;ControlTemplate>\n                &lt;dxg:GridControl Name=\"PART_GridControl\"\n                                 ColumnsSource=\"{Binding ElementName=root, Path=PopupColumns}\"\n                                 AutoGenerateColumns=\"None\">\n                    &lt;dxg:GridControl.View>\n                        &lt;dxg:TableView AutoWidth=\"True\"\n                                       ShowGroupPanel=\"False\"\n                                       EnableImmediatePosting=\"True\"\/>\n                    &lt;\/dxg:GridControl.View>\n                &lt;\/dxg:GridControl>\n            &lt;\/ControlTemplate>\n        &lt;\/dxg:LookUpEdit.PopupContentTemplate>\n    &lt;\/dxg:LookUpEdit>\n\n&lt;\/UserControl>\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">ExtendedLookUpEditor.xaml.cs<\/h2>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using DevExpress.Xpf.Editors;\nusing DevExpress.Xpf.Grid;\nusing System.Collections;\nusing System.Collections.ObjectModel;\nusing System.Windows;\nusing System.Windows.Controls;\n\nnamespace Views.Controls\n{\n    public partial class ExtendedLookUpEditor : UserControl\n    {\n        public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(\n            nameof(EditValue),\n            typeof(object),\n            typeof(ExtendedLookUpEditor),\n            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));\n\n        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(\n            nameof(ItemsSource),\n            typeof(IEnumerable),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(null));\n\n        public static readonly DependencyProperty ValueMemberProperty = DependencyProperty.Register(\n            nameof(ValueMember),\n            typeof(string),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(null, OnColumnsConfigChanged));\n\n        public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register(\n            nameof(DisplayMember),\n            typeof(string),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(null, OnColumnsConfigChanged));\n\n        public static readonly DependencyProperty HideValueColumnProperty = DependencyProperty.Register(\n            nameof(HideValueColumn),\n            typeof(bool),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(false, OnColumnsConfigChanged));\n\n        public static readonly DependencyProperty ValueColumnHeaderProperty = DependencyProperty.Register(\n            nameof(ValueColumnHeader),\n            typeof(string),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(null, OnColumnsConfigChanged));\n\n        public static readonly DependencyProperty DisplayColumnHeaderProperty = DependencyProperty.Register(\n            nameof(DisplayColumnHeader),\n            typeof(string),\n            typeof(ExtendedLookUpEditor),\n            new PropertyMetadata(null, OnColumnsConfigChanged));\n\n        public ObservableCollection&lt;GridColumn> PopupColumns { get; } = new ObservableCollection&lt;GridColumn>();\n\n        \/\/\/ &lt;summary>Wordt gevuurd wanneer de geselecteerde waarde verandert.&lt;\/summary>\n        public event EditValueChangedEventHandler EditValueChanged;\n\n        public ExtendedLookUpEditor()\n        {\n            InitializeComponent();\n            editor.EditValueChanged += (s, e) => EditValueChanged?.Invoke(this, e);\n        }\n\n        \/\/\/ &lt;summary>Het geselecteerde sleutelwaarde (gebonden aan ValueMember).&lt;\/summary>\n        public object EditValue\n        {\n            get => GetValue(EditValueProperty);\n            set => SetValue(EditValueProperty, value);\n        }\n\n        \/\/\/ &lt;summary>De lijst met keuze-opties.&lt;\/summary>\n        public IEnumerable ItemsSource\n        {\n            get => (IEnumerable)GetValue(ItemsSourceProperty);\n            set => SetValue(ItemsSourceProperty, value);\n        }\n\n        \/\/\/ &lt;summary>De property naam van het Id\/sleutelveld in de opties (bijv. \"Code\").&lt;\/summary>\n        public string ValueMember\n        {\n            get => (string)GetValue(ValueMemberProperty);\n            set => SetValue(ValueMemberProperty, value);\n        }\n\n        \/\/\/ &lt;summary>De property naam van het weergaveveld in de opties (bijv. \"Description\").&lt;\/summary>\n        public string DisplayMember\n        {\n            get => (string)GetValue(DisplayMemberProperty);\n            set => SetValue(DisplayMemberProperty, value);\n        }\n\n        \/\/\/ &lt;summary>Verberg de ValueMember kolom in de popup (default: false).&lt;\/summary>\n        public bool HideValueColumn\n        {\n            get => (bool)GetValue(HideValueColumnProperty);\n            set => SetValue(HideValueColumnProperty, value);\n        }\n\n        \/\/\/ &lt;summary>Kolomtitel voor de ValueMember kolom. Standaard de property naam.&lt;\/summary>\n        public string ValueColumnHeader\n        {\n            get => (string)GetValue(ValueColumnHeaderProperty);\n            set => SetValue(ValueColumnHeaderProperty, value);\n        }\n\n        \/\/\/ &lt;summary>Kolomtitel voor de DisplayMember kolom. Standaard de property naam.&lt;\/summary>\n        public string DisplayColumnHeader\n        {\n            get => (string)GetValue(DisplayColumnHeaderProperty);\n            set => SetValue(DisplayColumnHeaderProperty, value);\n        }\n\n        private static void OnColumnsConfigChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)\n            => ((ExtendedLookUpEditor)d).RebuildColumns();\n\n        private void RebuildColumns()\n        {\n            PopupColumns.Clear();\n\n            var valueMember = ValueMember;\n            var displayMember = DisplayMember;\n\n            if (string.IsNullOrEmpty(valueMember) &amp;&amp; string.IsNullOrEmpty(displayMember))\n                return;\n\n            if (!string.IsNullOrEmpty(valueMember) &amp;&amp; !HideValueColumn)\n            {\n                PopupColumns.Add(new GridColumn\n                {\n                    FieldName = valueMember,\n                    Header = ValueColumnHeader ?? valueMember,\n                    Width = 100\n                });\n            }\n\n            if (!string.IsNullOrEmpty(displayMember))\n            {\n                PopupColumns.Add(new GridColumn\n                {\n                    FieldName = displayMember,\n                    Header = DisplayColumnHeader ?? displayMember,\n                    Width = 200\n                });\n            }\n        }\n    }\n}\n<\/pre><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">GeoCoordinatesEditor.xaml<\/h2>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"xml\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">&lt;UserControl x:Class=\"Views.Controls.GeoCoordinatesEditor\"\n             xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n             xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n             xmlns:dxe=\"http:\/\/schemas.devexpress.com\/winfx\/2008\/xaml\/editors\"\n             xmlns:domain=\"clr-namespace:Domain\"\n             x:Name=\"root\">\n\n    &lt;UserControl.Resources>\n        &lt;domain:GeoCoordinatesDisplayConverter x:Key=\"GeoDisplayConverter\" DisplayDecimals=\"5\"\/>\n    &lt;\/UserControl.Resources>\n\n    &lt;Grid>\n        &lt;Grid.ColumnDefinitions>\n            &lt;ColumnDefinition Width=\"*\"\/>\n            &lt;ColumnDefinition Width=\"Auto\"\/>\n            &lt;ColumnDefinition Width=\"Auto\"\/>\n        &lt;\/Grid.ColumnDefinitions>\n\n        &lt;dxe:TextEdit Grid.Column=\"0\"\n                      x:Name=\"editor\"\n                      EditValue=\"{Binding ElementName=root, Path=Coordinates, Mode=TwoWay, UpdateSourceTrigger=LostFocus}\"\n                      DisplayTextConverter=\"{StaticResource GeoDisplayConverter}\"\/>\n\n        &lt;!-- Kopieer naar klembord (afgerond op 7 decimalen) -->\n        &lt;Button Grid.Column=\"1\"\n                Width=\"24\" Height=\"24\"\n                Margin=\"2,0,0,0\"\n                VerticalAlignment=\"Center\"\n                ToolTip=\"Co\u00f6rdinaten kopi\u00ebren naar klembord\"\n                Click=\"CopyButton_Click\"\n                Padding=\"0\"\n                Focusable=\"False\">\n            &lt;TextBlock Text=\"\ud83d\udccb\" FontSize=\"14\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\/>\n        &lt;\/Button>\n\n        &lt;!-- Openen in Google Maps -->\n        &lt;Button Grid.Column=\"2\"\n                Width=\"24\" Height=\"24\"\n                Margin=\"2,0,0,0\"\n                VerticalAlignment=\"Center\"\n                ToolTip=\"Openen in Google Maps\"\n                Click=\"MapsButton_Click\"\n                Padding=\"0\"\n                Focusable=\"False\">\n            &lt;TextBlock Text=\"\ud83d\uddfa\" FontSize=\"14\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\/>\n        &lt;\/Button>\n    &lt;\/Grid>\n\n&lt;\/UserControl>\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">GeoCoordinatesEditor.xaml.cs<\/h2>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System.Diagnostics;\nusing System.Windows;\nusing System.Windows.Controls;\nusing Domain;\n\nnamespace Views.Controls\n{\n    public partial class GeoCoordinatesEditor : UserControl\n    {\n        public static readonly DependencyProperty CoordinatesProperty = DependencyProperty.Register(\n            nameof(Coordinates),\n            typeof(string),\n            typeof(GeoCoordinatesEditor),\n            new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));\n\n        public GeoCoordinatesEditor()\n        {\n            InitializeComponent();\n        }\n\n        \/\/\/ &lt;summary>\n        \/\/\/ Co\u00f6rdinatenstring in Google Maps formaat, bijv. \"51.83082564125974, 4.585666568317119\".\n        \/\/\/ Gebruik &lt;see cref=\"Domain.GeoCoordinatesHelper\"\/> om de afzonderlijke lat\/lon doubles op te halen.\n        \/\/\/ &lt;\/summary>\n        public string Coordinates\n        {\n            get => (string)GetValue(CoordinatesProperty);\n            set => SetValue(CoordinatesProperty, value);\n        }\n\n        private void CopyButton_Click(object sender, RoutedEventArgs e)\n        {\n            if (GeoCoordinates.TryParse(Coordinates, out var coords))\n                Clipboard.SetText(coords.ToString());\n        }\n\n        private void MapsButton_Click(object sender, RoutedEventArgs e)\n        {\n            if (GeoCoordinates.TryParse(Coordinates, out var coords))\n            {\n                var url = string.Format(\n                    System.Globalization.CultureInfo.InvariantCulture,\n                    \"https:\/\/www.google.com\/maps\/@{0},{1},500m\",\n                    coords.Latitude, coords.Longitude);\n                Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });\n            }\n        }\n    }\n}\n\nusing System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace Domain\n{\n    \/\/\/ &lt;summary>\n    \/\/\/ Immutable waardetype dat een geografische co\u00f6rdinaat (lat, lon) vasthoudt.\n    \/\/\/ Kan worden opgeslagen als \u00e9\u00e9n string in het formaat dat Google Maps gebruikt:\n    \/\/\/ \"51.83082564125974, 4.585666568317119\"\n    \/\/\/ &lt;\/summary>\n    public sealed class GeoCoordinates\n    {\n        private static readonly char[] Separators = { ',', ';' };\n\n        public double Latitude { get; }\n        public double Longitude { get; }\n\n        public bool IsEmpty => Latitude == 0d &amp;&amp; Longitude == 0d;\n\n        public GeoCoordinates(double latitude, double longitude)\n        {\n            Latitude = latitude;\n            Longitude = longitude;\n        }\n\n        \/\/\/ &lt;summary>\n        \/\/\/ Probeert een co\u00f6rdinatenstring te parsen naar een &lt;see cref=\"GeoCoordinates\"\/> instantie.\n        \/\/\/ Accepteert het formaat \"lat, lon\" of \"lat; lon\" (decimaalteken punt of komma per getal).\n        \/\/\/ &lt;\/summary>\n        public static bool TryParse(string value, out GeoCoordinates result)\n        {\n            result = Empty;\n            if (string.IsNullOrWhiteSpace(value))\n                return false;\n\n            var parts = value.Split(Separators, StringSplitOptions.RemoveEmptyEntries);\n            if (parts.Length &lt; 2)\n                return false;\n\n            if (!TryParseDouble(parts[0].Trim(), out var lat) ||\n                !TryParseDouble(parts[1].Trim(), out var lon))\n                return false;\n\n            result = new GeoCoordinates(lat, lon);\n            return true;\n        }\n\n        \/\/\/ &lt;summary>Aantal decimalen voor opslag (\u22481cm nauwkeurigheid).&lt;\/summary>\n        public const int StorageDecimals = 7;\n\n        \/\/\/ &lt;summary>Formatteert als \"lat, lon\" string afgerond op &lt;see cref=\"StorageDecimals\"\/> decimalen (voor opslag).&lt;\/summary>\n        public override string ToString()\n        {\n            if (IsEmpty) return string.Empty;\n            var fmt = \"F\" + StorageDecimals;\n            return string.Format(CultureInfo.InvariantCulture, \"{0}, {1}\",\n                Math.Round(Latitude,  StorageDecimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture),\n                Math.Round(Longitude, StorageDecimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture));\n        }\n\n        \/\/\/ &lt;summary>\n        \/\/\/ Formatteert als leesbare weergavestring afgerond op &lt;paramref name=\"decimals\"\/> decimalen.\n        \/\/\/ 5 decimalen \u2248 1m nauwkeurigheid (ruim voldoende voor ~15m GPS nauwkeurigheid).\n        \/\/\/ &lt;\/summary>\n        public string ToDisplayString(int decimals = 5)\n        {\n            if (IsEmpty) return string.Empty;\n            var fmt = \"F\" + decimals;\n            return string.Format(CultureInfo.InvariantCulture, \"{0}, {1}\",\n                Math.Round(Latitude,  decimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture),\n                Math.Round(Longitude, decimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture));\n        }\n\n        public static readonly GeoCoordinates Empty = new GeoCoordinates(0d, 0d);\n\n        private static bool TryParseDouble(string s, out double value)\n        {\n            return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value)\n                || double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out value);\n        }\n    }\n\n    \/\/\/ &lt;summary>Helper-methoden voor het werken met co\u00f6rdinaten als string property.&lt;\/summary>\n    public static class GeoCoordinatesHelper\n    {\n        \/\/\/ &lt;summary>Leest een &lt;see cref=\"GeoCoordinates\"\/> uit een co\u00f6rdinatenstring. Geeft &lt;see cref=\"GeoCoordinates.Empty\"\/> terug als de string leeg of ongeldig is.&lt;\/summary>\n        public static GeoCoordinates Parse(string coordinatesString)\n            => GeoCoordinates.TryParse(coordinatesString, out var result) ? result : GeoCoordinates.Empty;\n\n        \/\/\/ &lt;summary>Formatteert een &lt;see cref=\"GeoCoordinates\"\/> naar een string met volledige precisie.&lt;\/summary>\n        public static string Format(GeoCoordinates coordinates)\n            => coordinates?.ToString() ?? string.Empty;\n\n        \/\/\/ &lt;summary>Formatteert losse lat\/lon doubles naar een co\u00f6rdinatenstring met volledige precisie.&lt;\/summary>\n        public static string Format(double latitude, double longitude)\n            => Format(new GeoCoordinates(latitude, longitude));\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ WPF IValueConverter die een co\u00f6rdinatenstring omzet naar een afgeronde weergavestring,\n    \/\/\/ bedoeld als DisplayTextConverter voor een DevExpress TextEdit.\n    \/\/\/ EditValue behoudt de volledige precisie-string; de weergave toont 5 decimalen (\u22481m, ~15m GPS).\n    \/\/\/ &lt;\/summary>\n    public sealed class GeoCoordinatesDisplayConverter : IValueConverter\n    {\n        \/\/\/ &lt;summary>Aantal decimalen voor de weergave. Default 5 (\u22481m nauwkeurigheid).&lt;\/summary>\n        public int DisplayDecimals { get; set; } = 5;\n\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var coords = GeoCoordinatesHelper.Parse(value as string);\n            return coords.IsEmpty ? string.Empty : coords.ToDisplayString(DisplayDecimals);\n        }\n\n        \/\/\/ &lt;summary>Bij bewerken typt de gebruiker de volledige string; EditValue wordt ongewijzigd doorgegeven.&lt;\/summary>\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n            => value;\n    }\n}\n\n<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>ExtendedLookUpEditor.xaml ExtendedLookUpEditor.xaml.cs GeoCoordinatesEditor.xaml GeoCoordinatesEditor.xaml.cs<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-10151","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/10151","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=10151"}],"version-history":[{"count":3,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/10151\/revisions"}],"predecessor-version":[{"id":10156,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/10151\/revisions\/10156"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=10151"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=10151"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=10151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}