ExtendedLookUpEditor.xaml
<UserControl x:Class="Views.Controls.ExtendedLookUpEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
x:Name="root">
<dxg:LookUpEdit x:Name="editor"
MinWidth="0"
AutoPopulateColumns="False"
ValueMember="{Binding ElementName=root, Path=ValueMember}"
DisplayMember="{Binding ElementName=root, Path=DisplayMember}"
ItemsSource="{Binding ElementName=root, Path=ItemsSource}"
EditValue="{Binding ElementName=root, Path=EditValue, Mode=TwoWay}">
<dxg:LookUpEdit.StyleSettings>
<dxg:SearchLookUpEditStyleSettings AllowGrouping="False"/>
</dxg:LookUpEdit.StyleSettings>
<dxg:LookUpEdit.PopupContentTemplate>
<ControlTemplate>
<dxg:GridControl Name="PART_GridControl"
ColumnsSource="{Binding ElementName=root, Path=PopupColumns}"
AutoGenerateColumns="None">
<dxg:GridControl.View>
<dxg:TableView AutoWidth="True"
ShowGroupPanel="False"
EnableImmediatePosting="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</ControlTemplate>
</dxg:LookUpEdit.PopupContentTemplate>
</dxg:LookUpEdit>
</UserControl>
ExtendedLookUpEditor.xaml.cs
using DevExpress.Xpf.Editors;
using DevExpress.Xpf.Grid;
using System.Collections;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace Views.Controls
{
public partial class ExtendedLookUpEditor : UserControl
{
public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(
nameof(EditValue),
typeof(object),
typeof(ExtendedLookUpEditor),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(null));
public static readonly DependencyProperty ValueMemberProperty = DependencyProperty.Register(
nameof(ValueMember),
typeof(string),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(null, OnColumnsConfigChanged));
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register(
nameof(DisplayMember),
typeof(string),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(null, OnColumnsConfigChanged));
public static readonly DependencyProperty HideValueColumnProperty = DependencyProperty.Register(
nameof(HideValueColumn),
typeof(bool),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(false, OnColumnsConfigChanged));
public static readonly DependencyProperty ValueColumnHeaderProperty = DependencyProperty.Register(
nameof(ValueColumnHeader),
typeof(string),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(null, OnColumnsConfigChanged));
public static readonly DependencyProperty DisplayColumnHeaderProperty = DependencyProperty.Register(
nameof(DisplayColumnHeader),
typeof(string),
typeof(ExtendedLookUpEditor),
new PropertyMetadata(null, OnColumnsConfigChanged));
public ObservableCollection<GridColumn> PopupColumns { get; } = new ObservableCollection<GridColumn>();
/// <summary>Wordt gevuurd wanneer de geselecteerde waarde verandert.</summary>
public event EditValueChangedEventHandler EditValueChanged;
public ExtendedLookUpEditor()
{
InitializeComponent();
editor.EditValueChanged += (s, e) => EditValueChanged?.Invoke(this, e);
}
/// <summary>Het geselecteerde sleutelwaarde (gebonden aan ValueMember).</summary>
public object EditValue
{
get => GetValue(EditValueProperty);
set => SetValue(EditValueProperty, value);
}
/// <summary>De lijst met keuze-opties.</summary>
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
/// <summary>De property naam van het Id/sleutelveld in de opties (bijv. "Code").</summary>
public string ValueMember
{
get => (string)GetValue(ValueMemberProperty);
set => SetValue(ValueMemberProperty, value);
}
/// <summary>De property naam van het weergaveveld in de opties (bijv. "Description").</summary>
public string DisplayMember
{
get => (string)GetValue(DisplayMemberProperty);
set => SetValue(DisplayMemberProperty, value);
}
/// <summary>Verberg de ValueMember kolom in de popup (default: false).</summary>
public bool HideValueColumn
{
get => (bool)GetValue(HideValueColumnProperty);
set => SetValue(HideValueColumnProperty, value);
}
/// <summary>Kolomtitel voor de ValueMember kolom. Standaard de property naam.</summary>
public string ValueColumnHeader
{
get => (string)GetValue(ValueColumnHeaderProperty);
set => SetValue(ValueColumnHeaderProperty, value);
}
/// <summary>Kolomtitel voor de DisplayMember kolom. Standaard de property naam.</summary>
public string DisplayColumnHeader
{
get => (string)GetValue(DisplayColumnHeaderProperty);
set => SetValue(DisplayColumnHeaderProperty, value);
}
private static void OnColumnsConfigChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> ((ExtendedLookUpEditor)d).RebuildColumns();
private void RebuildColumns()
{
PopupColumns.Clear();
var valueMember = ValueMember;
var displayMember = DisplayMember;
if (string.IsNullOrEmpty(valueMember) && string.IsNullOrEmpty(displayMember))
return;
if (!string.IsNullOrEmpty(valueMember) && !HideValueColumn)
{
PopupColumns.Add(new GridColumn
{
FieldName = valueMember,
Header = ValueColumnHeader ?? valueMember,
Width = 100
});
}
if (!string.IsNullOrEmpty(displayMember))
{
PopupColumns.Add(new GridColumn
{
FieldName = displayMember,
Header = DisplayColumnHeader ?? displayMember,
Width = 200
});
}
}
}
}
GeoCoordinatesEditor.xaml
<UserControl x:Class="Views.Controls.GeoCoordinatesEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:domain="clr-namespace:Domain"
x:Name="root">
<UserControl.Resources>
<domain:GeoCoordinatesDisplayConverter x:Key="GeoDisplayConverter" DisplayDecimals="5"/>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<dxe:TextEdit Grid.Column="0"
x:Name="editor"
EditValue="{Binding ElementName=root, Path=Coordinates, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
DisplayTextConverter="{StaticResource GeoDisplayConverter}"/>
<!-- Kopieer naar klembord (afgerond op 7 decimalen) -->
<Button Grid.Column="1"
Width="24" Height="24"
Margin="2,0,0,0"
VerticalAlignment="Center"
ToolTip="Coördinaten kopiëren naar klembord"
Click="CopyButton_Click"
Padding="0"
Focusable="False">
<TextBlock Text="📋" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button>
<!-- Openen in Google Maps -->
<Button Grid.Column="2"
Width="24" Height="24"
Margin="2,0,0,0"
VerticalAlignment="Center"
ToolTip="Openen in Google Maps"
Click="MapsButton_Click"
Padding="0"
Focusable="False">
<TextBlock Text="🗺" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button>
</Grid>
</UserControl>
GeoCoordinatesEditor.xaml.cs
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using Domain;
namespace Views.Controls
{
public partial class GeoCoordinatesEditor : UserControl
{
public static readonly DependencyProperty CoordinatesProperty = DependencyProperty.Register(
nameof(Coordinates),
typeof(string),
typeof(GeoCoordinatesEditor),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public GeoCoordinatesEditor()
{
InitializeComponent();
}
/// <summary>
/// Coördinatenstring in Google Maps formaat, bijv. "51.83082564125974, 4.585666568317119".
/// Gebruik <see cref="Domain.GeoCoordinatesHelper"/> om de afzonderlijke lat/lon doubles op te halen.
/// </summary>
public string Coordinates
{
get => (string)GetValue(CoordinatesProperty);
set => SetValue(CoordinatesProperty, value);
}
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
if (GeoCoordinates.TryParse(Coordinates, out var coords))
Clipboard.SetText(coords.ToString());
}
private void MapsButton_Click(object sender, RoutedEventArgs e)
{
if (GeoCoordinates.TryParse(Coordinates, out var coords))
{
var url = string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"https://www.google.com/maps/@{0},{1},500m",
coords.Latitude, coords.Longitude);
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
}
}
}
using System;
using System.Globalization;
using System.Windows.Data;
namespace Domain
{
/// <summary>
/// Immutable waardetype dat een geografische coördinaat (lat, lon) vasthoudt.
/// Kan worden opgeslagen als één string in het formaat dat Google Maps gebruikt:
/// "51.83082564125974, 4.585666568317119"
/// </summary>
public sealed class GeoCoordinates
{
private static readonly char[] Separators = { ',', ';' };
public double Latitude { get; }
public double Longitude { get; }
public bool IsEmpty => Latitude == 0d && Longitude == 0d;
public GeoCoordinates(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
/// <summary>
/// Probeert een coördinatenstring te parsen naar een <see cref="GeoCoordinates"/> instantie.
/// Accepteert het formaat "lat, lon" of "lat; lon" (decimaalteken punt of komma per getal).
/// </summary>
public static bool TryParse(string value, out GeoCoordinates result)
{
result = Empty;
if (string.IsNullOrWhiteSpace(value))
return false;
var parts = value.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
return false;
if (!TryParseDouble(parts[0].Trim(), out var lat) ||
!TryParseDouble(parts[1].Trim(), out var lon))
return false;
result = new GeoCoordinates(lat, lon);
return true;
}
/// <summary>Aantal decimalen voor opslag (≈1cm nauwkeurigheid).</summary>
public const int StorageDecimals = 7;
/// <summary>Formatteert als "lat, lon" string afgerond op <see cref="StorageDecimals"/> decimalen (voor opslag).</summary>
public override string ToString()
{
if (IsEmpty) return string.Empty;
var fmt = "F" + StorageDecimals;
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}",
Math.Round(Latitude, StorageDecimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture),
Math.Round(Longitude, StorageDecimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture));
}
/// <summary>
/// Formatteert als leesbare weergavestring afgerond op <paramref name="decimals"/> decimalen.
/// 5 decimalen ≈ 1m nauwkeurigheid (ruim voldoende voor ~15m GPS nauwkeurigheid).
/// </summary>
public string ToDisplayString(int decimals = 5)
{
if (IsEmpty) return string.Empty;
var fmt = "F" + decimals;
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}",
Math.Round(Latitude, decimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture),
Math.Round(Longitude, decimals, MidpointRounding.AwayFromZero).ToString(fmt, CultureInfo.InvariantCulture));
}
public static readonly GeoCoordinates Empty = new GeoCoordinates(0d, 0d);
private static bool TryParseDouble(string s, out double value)
{
return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value)
|| double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out value);
}
}
/// <summary>Helper-methoden voor het werken met coördinaten als string property.</summary>
public static class GeoCoordinatesHelper
{
/// <summary>Leest een <see cref="GeoCoordinates"/> uit een coördinatenstring. Geeft <see cref="GeoCoordinates.Empty"/> terug als de string leeg of ongeldig is.</summary>
public static GeoCoordinates Parse(string coordinatesString)
=> GeoCoordinates.TryParse(coordinatesString, out var result) ? result : GeoCoordinates.Empty;
/// <summary>Formatteert een <see cref="GeoCoordinates"/> naar een string met volledige precisie.</summary>
public static string Format(GeoCoordinates coordinates)
=> coordinates?.ToString() ?? string.Empty;
/// <summary>Formatteert losse lat/lon doubles naar een coördinatenstring met volledige precisie.</summary>
public static string Format(double latitude, double longitude)
=> Format(new GeoCoordinates(latitude, longitude));
}
/// <summary>
/// WPF IValueConverter die een coördinatenstring omzet naar een afgeronde weergavestring,
/// bedoeld als DisplayTextConverter voor een DevExpress TextEdit.
/// EditValue behoudt de volledige precisie-string; de weergave toont 5 decimalen (≈1m, ~15m GPS).
/// </summary>
public sealed class GeoCoordinatesDisplayConverter : IValueConverter
{
/// <summary>Aantal decimalen voor de weergave. Default 5 (≈1m nauwkeurigheid).</summary>
public int DisplayDecimals { get; set; } = 5;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var coords = GeoCoordinatesHelper.Parse(value as string);
return coords.IsEmpty ? string.Empty : coords.ToDisplayString(DisplayDecimals);
}
/// <summary>Bij bewerken typt de gebruiker de volledige string; EditValue wordt ongewijzigd doorgegeven.</summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value;
}
}
1015100cookie-checkDevExpress WPF Components