Laden...

Programm metrisch <=> UI in Zoll (Umrechnung)

Erstellt von Turmoil vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.328 Views
T
Turmoil Themenstarter:in
60 Beiträge seit 2008
vor 6 Jahren
Programm metrisch <=> UI in Zoll (Umrechnung)

Hallo und ein gutes neues Jahr.

Unser Programm rechnet mit verschiedenen Werten (alle metrisch) abhängige Größen aus. Diese werden dann weiter verarbeitet...
In der Datenbank stehen alle Größen metrisch. Die Datenbankanbindung an die Klasse passt m.E..
Wie würdet ihr eine Umrechnung (metrisch-Zoll) des UI realisieren? Eine Hilfsklasse habe ich schon implementiert, um Wertumrechnungen in Meldungen etc. per Reflektion zu automatisieren.

Welche Möglichkeiten der UI gibt es? Binding überschreiben, alles selber schreiben und keine Bindung nutzen?

Grüße Turmoil

3.003 Beiträge seit 2006
vor 6 Jahren

In WinForms? Den Controller splitten in abstrakten BasisController mit Implementierungen für anzuzeigende Skalen. Dann kannst du on the fly den Controller austauschen und kriegst die passenden Werte in den gebundenen Steuerelementen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

T
Turmoil Themenstarter:in
60 Beiträge seit 2008
vor 6 Jahren

Ja, klassisch WinForms.

Den Controller splitten in abstrakten BasisController mit Implementierungen für anzuzeigende Skalen.

Welchen Controller? Ich habe bisher eine Textbox in einem Form, eine Instanz einer Klasse mit einer Property A, die ich über eine Bindung an die Textbox gebunden habe.

Grüße Turmoil

3.003 Beiträge seit 2006
vor 6 Jahren

Die Aufteilung der Zuständigkeiten wird in WinForms so gut wie immer über das MVC-Pattern (Model-View-Controller) realisiert, DEN Controller meine ich. Ich war davon ausgegangen, dass, wenn ihr schon lobenswerterweise DataBinding benutzt, die Trennung entsprechend auch realisiert ist (alles andere würde nicht sehr viel Sinn ergeben). Falls das nicht so ist, müsstet ihr das sowieso nachholen bzw konsequenter umsetzen: so, wie ich dich verstehe, ist deine Klasse mit der Property A
der Kern eines zu entwickelnden Controllers. Das ist nicht so viel Aufwand, wie es klingt, ich würde vermuten, dass ihr da schon mehr als die Hälfte von dem habt, was ihr braucht.

([Artikel] Drei-Schichten-Architektur)

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

6.911 Beiträge seit 2009
vor 6 Jahren

Hallo Turmoil,

anbei eine Möglichkeit wie das mit der Basisklasse (ich habs BaseViewModel genannt) und per Expressions gehen kann. Den Code kannst du beliebig weiter ausbauen.


public abstract class BaseViewModel : INotifyPropertyChanged
{
    private static readonly Dictionary<Key, Delegate> _delegateCache;

    static BaseViewModel() => _delegateCache = new Dictionary<Key, Delegate>();

    protected virtual TProperty GetValue<TObject, TProperty>(TObject obj, string propertyName)
    {
        var key = new Key(typeof(TObject), propertyName, true);
        Func<TObject, TProperty> getterFunc = null;

        if (_delegateCache.TryGetValue(key, out Delegate del))
            getterFunc = del as Func<TObject, TProperty>;
        else
        {
            var getExpression = GetGetExpression<TObject, TProperty>(propertyName) as Expression<Func<TObject, TProperty>>;
            getterFunc        = getExpression.Compile();
            _delegateCache.Add(key, getterFunc);
        }

        TProperty value = getterFunc(obj);
        return value;
    }

    protected virtual void SetValue<TObject, TProperty>(TObject obj, string propertyName, TProperty value)
    {
        var key = new Key(typeof(TObject), propertyName, false);
        Action<TObject, TProperty> setterAction = null;

        if (_delegateCache.TryGetValue(key, out Delegate del))
            setterAction = del as Action<TObject, TProperty>;
        else
        {
            var setExpression = GetSetExpression<TObject, TProperty>(propertyName) as Expression<Action<TObject, TProperty>>;
            setterAction      = setExpression.Compile();
            _delegateCache.Add(key, setterAction);
        }

        setterAction(obj, value);
        this.OnPropertyChanged(propertyName);
    }

    private static Expression GetGetExpression<TObject, TProperty>(string propertyName)
    {
        ParameterExpression paramExpression = Expression.Parameter(typeof(TObject));
        MemberExpression propertyExpression = Expression.Property(paramExpression, propertyName);
        return Expression.Lambda<Func<TObject, TProperty>>(propertyExpression, paramExpression);
    }

    private static Expression GetSetExpression<TObject, TProperty>(string propertyName)
    {
        ParameterExpression paramExpression  = Expression.Parameter(typeof(TObject));
        MemberExpression propertyExpression  = Expression.Property(paramExpression, propertyName);
        ParameterExpression paramExpression2 = Expression.Parameter(typeof(TProperty), propertyName);
        BinaryExpression assignExpression    = Expression.Assign(propertyExpression, paramExpression2);
        return Expression.Lambda<Action<TObject, TProperty>>(assignExpression, paramExpression, paramExpression2);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    [DebuggerNonUserCode]
    private readonly struct Key : IEquatable<Key>
    {
        public Type Type           { get; }
        public string PropertyName { get; }
        public bool IsGetter       { get; }

        public Key(Type type, string propertyName, bool isGetter)
        {
            this.Type         = type;
            this.PropertyName = propertyName;
            this.IsGetter     = isGetter;
        }

        public bool Equals(Key other)
            => this.Type         == other.Type
            && this.PropertyName == other.PropertyName
            && this.IsGetter     == other.IsGetter;

        public override bool Equals(object obj) => obj is Key other && this.Equals(other);

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = (int)17;
                hash = hash * 23 + this.Type.GetHashCode();
                hash = hash * 23 + this.PropertyName.GetHashCode();
                hash = hash * 23 + this.IsGetter.GetHashCode();
                return hash;
            }
        }
    }
}


public interface IMetricImpericalConverter
{
    double InchToMillimeter(double value);
    double MillimeterToInch(double value);
}

public class MetricImpericalConverter : IMetricImpericalConverter
{
    private const double OneInchInMM = 25.4;

    public double InchToMillimeter(double value) => value * OneInchInMM;
    public double MillimeterToInch(double value) => value / OneInchInMM;
}


public abstract class MetricalImpericalBaseViewModel : BaseViewModel
{
    private readonly IMetricImpericalConverter _metricImpericalConverter;
    private bool                               _isMetric = true;

    protected MetricalImpericalBaseViewModel(IMetricImpericalConverter metricImpericalConverter = null)
        => _metricImpericalConverter = metricImpericalConverter ?? new MetricImpericalConverter();

    public bool IsMetric
    {
        get => _isMetric;
        set
        {
            _isMetric = value;
            this.OnPropertyChanged(nameof(this.IsMetric));
        }
    }

    protected virtual double GetValue<TObject>(TObject obj, string propertyName)
    {
        double value = this.GetValue<TObject, double>(obj, propertyName);

        return _isMetric ? value : _metricImpericalConverter.MillimeterToInch(value);
    }

    protected virtual void SetValue<TObject>(TObject obj, string propertyName, double value)
    {
        if (!_isMetric)
            value = _metricImpericalConverter.InchToMillimeter(value);

        this.SetValue<TObject, double>(obj, propertyName, value);
    }
}


// Model
public class Rectangle
{
    public double Lenght { get; set; }     // [mm]
    public double Height { get; set; }     // [mm]
}

// ViewModel
public class RectangelViewModel : MetricalImpericalBaseViewModel
{
    private readonly Rectangle _rectangle;

    public RectangelViewModel(Rectangle rectangle) => _rectangle = rectangle;

    public double Length
    {
        get => this.GetValue(_rectangle, nameof(_rectangle.Lenght));
        set => this.SetValue(_rectangle, nameof(_rectangle.Lenght), value);
    }

    public double Height
    {
        get => this.GetValue(_rectangle, nameof(_rectangle.Height));
        set => this.SetValue(_rectangle, nameof(_rectangle.Height), value);
    }
}


public partial class Form1 : Form
{
    private readonly RectangelViewModel _viewModel;
    private readonly BindingSource      _bindingSource;

    public Form1()
    {
        InitializeComponent();

        var rectangle = new Rectangle { Lenght = 100, Height = 50 };
        _viewModel    = new RectangelViewModel(rectangle);

        // Der folgende Code könnte auch vom VS-Designer generiert werden, hier aber explizit zur
        // Veranschaulichung.
        _bindingSource = new BindingSource();
        _bindingSource.DataSource = _viewModel;
        lenghtTextBox.DataBindings.Add(new Binding(nameof(TextBox.Text), _bindingSource, nameof(_viewModel.Length), true, DataSourceUpdateMode.OnValidation));
        heightTextBox.DataBindings.Add(new Binding(nameof(TextBox.Text), _bindingSource, nameof(_viewModel.Height), true, DataSourceUpdateMode.OnValidation));
        metricCheckBox.DataBindings.Add(new Binding(nameof(CheckBox.Checked), _bindingSource, nameof(_viewModel.IsMetric), false, DataSourceUpdateMode.OnPropertyChanged));
    }
}

Den Code hab ich eben nur runterhackt, daher sind keine Argument-Validierungen, Tests, etc. dabei. Sonst lässt sich das sicher auch noch verbessern, aber als Idee sollte es reichen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

T
Turmoil Themenstarter:in
60 Beiträge seit 2008
vor 6 Jahren

Hallo.

Natürlich haben wir Controller (MVC), die den Programmablauf etc. behandeln. Die Properties unseres Modells (und das sind einige) sind bisher jedoch direkt an Textboxen angebunden.

Ich dachte ich könnte das Umrechnen einfach in der Binding-Klasse o.ä. also in der UI-Schicht abfrühstücken, sonst müsste ich ja jede Property des Modells zusätzlich in einem Controller implementieren.

Des Weiteren werden auch Geschwindigkeiten umgerechnet, also nicht nur Property x 25,4.

Aber danke für eure Anregungen.

Grüße Turmoil

6.911 Beiträge seit 2009
vor 6 Jahren

Hallo Turmoil,

Des Weiteren werden auch Geschwindigkeiten umgerechnet, also nicht nur Property x 25,4.

Gut, aber wo ist das Problem? Das lässt sich ja alles im Beispiel oben erweitern, nur ich mach das nicht mehr 😉

Da wäre als Weg besser nicht direkt den Zahlenwert, sondern den Wert (Zahlenwert + Einheit) als Eigenschaft zu haben, so dass dann beim Umrechnen auch der passende Kontext (Distanz, Distanz pro Zeit, Temperatur, etc.) vorhanden ist. Dazu einen passenden Typen erstellen und diesen verwenden und an den Konverter weiterreichen, der soll dann wissen wie umgerechnet wird.

einfach in der Binding-Klasse o.ä. also in der UI-Schicht abfrühstücken

Ich finde das gehört in das ViewModel. Im Forum gab es so eine Diskussion schon öfters (bezogen auf WPF, aber die Aussagen gelten auch hier): z.B. MVVM vs ValueConverter

Die View an sich ist und soll "dumm" sein und nur das Anzeigen was das ViewModel bereitstellt und in die andere Richtung dem ViewModel Benutzerinteraktionen weiterleiten.

PS: In WPF könnte die Möglichkeit per Converter / Binding verwendet werden, aber in WinForms ist das mehr Murks als Entwicklung. Daher würde ich diesen Weg erst recht nicht weiter verfolgen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

2.298 Beiträge seit 2010
vor 6 Jahren

Ich würde auch sagen, dass die Umrechnung im View nichts zu suchen hat. Ich würde mich da im ersten Schritt an einem Aufbau wie folgendem Versuchen:

=> View (Anzeige, Converterwechsel etc)
=> Controller(Änderungen am Model, wenn Convert gewechselt wird bzw. ein Wert geändert wird)
=> Model (Wert 1; Einheit 1 [durch Converter bestimmt]; Wert 2 [umgerechneter Wert]; Einheit 2 [durch Converter bestimmt], Liste aller verfügbaren Converter, gewählter Converter)

Du kannst im Controller anschließend auf Property-Changed des Models horchen und bei Eingabe eines Wertes bzw. bei wechsel des Converters entsprechend reagieren.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

T
Turmoil Themenstarter:in
60 Beiträge seit 2008
vor 6 Jahren

Guten Morgen.

Ich werde euren Rat befolgen und alle Properties des Models in den Controlern bereitstellen obwohl m. E. weder das Model noch die Controler eigentlich wissen müssten, welches System in der GUI eingestellt ist (ähnlich dem Dezimaltrennzeichen).

Da wäre als Weg besser nicht direkt den Zahlenwert, sondern den Wert (Zahlenwert + Einheit) als Eigenschaft zu haben, so dass dann beim Umrechnen auch der passende Kontext (Distanz, Distanz pro Zeit, Temperatur, etc.) vorhanden ist. Dazu einen passenden Typen erstellen und diesen verwenden und an den Konverter weiterreichen, der soll dann wissen wie umgerechnet wird.

Das habe ich im Model per Attribute und Extensions gelöst, damit ich in Meldungen den umgerechneten Wert ausgeben kann, falls erforderlich.

Grüße Turmoil

3.003 Beiträge seit 2006
vor 6 Jahren

Guten Morgen.

Ich werde euren Rat befolgen und alle Properties des Models in den Controlern bereitstellen obwohl m. E. weder das Model noch die Controler eigentlich wissen müssten, welches System in der GUI eingestellt ist (ähnlich dem Dezimaltrennzeichen).

Der Controller steuert die UI. Natürlich sollte er über ihren Zustand informiert sein. Tatsächlich ist er sogar der einzige, der ihren Zustand ändern darf. Das Model braucht diese Information wirklich nicht.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

T
Turmoil Themenstarter:in
60 Beiträge seit 2008
vor 6 Jahren

Moin.

Der Controller steuert die UI. Natürlich sollte er über ihren Zustand informiert sein. Tatsächlich ist er sogar der einzige, der ihren Zustand ändern darf. Das Model braucht diese Information wirklich nicht.

Da stimme ich überein, aber muss der Controler alle Daten zwischenspeichern nur weil der Benutzer die UI in einem anderen System eingestellt hat?

Grüße Turmoil

2.298 Beiträge seit 2010
vor 6 Jahren

Der Controller muss nichts zwischenspeichern. Der Controller darf doch gern direkt aufs Model greifen.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

3.003 Beiträge seit 2006
vor 6 Jahren

Die Klasse, die du oben erwähnt hast, mit Property A - wo du eine Instanz hast, an die dein Form gebunden ist - das ist eigentlich schon ein Controller. Er vermittelt zwischen View und Model(s) und steuert das Verhalten und den Zustand der beiden. Dazu braucht er eine Abbildung von eventuellen UI-Logiken (zB Validierung, interessiert uns an der Stelle nicht) sowie Kenntnis darüber, wie Daten aus dem Model bei ihrer Anzeige umzuformen sind. Punkt 1 lagerst du in eine Basisklasse aus, Punkt 2 in konkrete Implementierungen dort, wo sie sich unterscheiden, in deinem Fall eben bei den verwendeten Skalen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)