Laden...

Forenbeiträge von sugar76 Ingesamt 69 Beiträge

22.05.2021 - 11:37 Uhr

Das musst Du doch selbst wissen, ob Dir das reicht.

Da hast Du recht! Es ging mir halt darum, ob jemand eines der Verfahren aus eigener Erfahrung empfehlen kann.

Werd's wohl mit dem LicenseProvider machen, da hab ich die volle Kontrolle und bin nicht von externen Libs abhängig.

21.05.2021 - 18:24 Uhr

Besten Dank, hatte ich auch schon gesehen bzw. die Weiterentwicklungen Standard.Licensing und Standard.Licensing-5.0.

Ich frage mich vor allem, ob ich eine externe Lib überhaupt benötige oder es vollkommen ausreichend ist, den LicenseProvider + Private Key zu verwenden.

21.05.2021 - 16:38 Uhr

Hallo,

ich möchte in eine WPF-Anwendung einen Lizenzmechanismus einbauen.

Habe hier im Forum nichts dazu gefunden. Suche nach "Lizenz" u.ä. findet alles mögliche, nur nicht das was ich suche ...

Die Lizenz soll diverse Attribute wie z.B. Trialdauer, Max. Anzahl Benutzer, etc. beinhalten.

Es gibt kommerzielle Produkte wie z.B WinLicense.

Alternativ kann man eine Subklasse vom LicenseProvider verwenden und die Lizenz mit einem Private Key absichern, der Aufwand sieht überschaubar aus.

Meine Fragen:

  • Reicht der Ansatz mit dem LicenseProvider im Prinzip aus oder benötige ich ein kommerzielles Produkt?
  • Kann jemand ein Verfahren empfehlen?

Gruß 🙂

08.07.2020 - 15:44 Uhr

Ok danke für die Info.


>

Mir ist trotz Recherche nicht klar, ob ich ein Zertifikat mit Hardware-Token (EV) benötige oder ob ein "normales" Zertifikat ausreicht.

Wenn mich jemand aufklären könnte, wäre ich dankbar ... 😉

08.07.2020 - 10:00 Uhr

Hallo,

ich will meine WPF-Anwendung signieren.

Ich finde allerdings den Markt für Anbieter von Code Signing Zertifikaten recht unübersichtlich und es gibt extreme Preisunterschiede.

Außerdem erfordert der Registrierungsprozess bei einigen Anbietern anscheinend den Gang zum Notar (siehe dieser Beitrag).

Kurz: ich blick da nicht ganz durch.

Kann jemand einen seriösen Anbieter empfehlen, der preislich OK ist und bei dem der Registrierungsprozess möglichst unkompliziert abläuft?

Gruß 🙂

25.03.2020 - 13:52 Uhr

Hallo,

ich arbeite an einer WPF-Anwendung, die aktuell unter .NET 4.6.1 läuft. Clients sind alle Windows 10.

Ich will nun die .NET Version im Rahmen eines Wartungsupdates aktualisieren.

Nun ist die Frage, ob ich auf .NET 4.8 oder auf .NET Core 3.1 aktualisiere.

Bei .NET 4.8 habe ich keine Bedenken.

Bei .NET Core ist es anders:

  • laufen alle Bibliotheken unter .NET Core (z.B. Microsoft.ReportViewer.Winforms)?
  • läuft eine .NET Core Anwendung auf allen Windows 10 Clients?

Wie steht ihr dazu? Hat jemand schon mal eine WPF-Anwendung auf .NET Core migriert und welche Probleme gab es dabei?

Gruß 🙂

05.11.2019 - 16:02 Uhr

Hallo allerseits,

ich benötige für meine WPF-Anwendung einen Installer, der als Datei (MSI oder EXE) ausgeliefert wird.

Ich habe das bisher mit WiX gemacht. Das geht zwar, ich finde aber die XML-Syntax nicht sehr intuitiv. Vor allem, was die Dialog-Steuerung geht, komme ich regelmäßig ins Schwitzen.

Daher meine Frage: womit/wie baut Ihr den Installer? Wie sind Eure Erfahrungen WiX bzw. könnt Ihr Alternativen empfehlen?

Gruß 🙂

18.07.2019 - 14:05 Uhr

Habe es gerade gebraucht - danke!

21.02.2019 - 12:10 Uhr

Manchmal hilft es, das Problem zu formulieren und dann kommt man von selbst auf die Lösung ... 😁

Ich habe es einfach so gelöst, dass ich vor dem Matchen die Zeichenfolge '❔' durch '' ersetze. Dann gibt es beim Matchen keine Konflikte mit den anderen Doppelpunkten.

Als Regex verwende ich dann statt

:+([\w\d(\:question:)]+)

diese:

:+([\w\d\\]+)
21.02.2019 - 10:54 Uhr

Hallo,

ein besserer Titel für meine Frage ist mir leider nicht eingefallen 😉

Ich suche einen regulären Ausdruck, der ein Wort matcht, welches folgendes enthält (alles optional):

  • Buchstaben
  • Ziffern
  • Die Zeichenfolge
    Wichtig: Doppelpunkte ohne vorangestelltes Fragezeichen sind nicht im Wort enthalten. Wenn Doppelpunkt, dann muss davor ein Fragezeichen kommen.

Den gesuchten Wörtern ist immer eine Reihe von Doppelpunkten vorangestellt.

Beispiel (die gesuchten Wörter in großer Schrift):
::<>mT❔MFN1:********0340+SO❔iPhone

Mein Ansatz ist, die Wörter mittels Capture Groups auszulesen. Folgende Regex ist mir bisher eingefallen:

:+([\w\d(\:question:)]+)

Das funktioniert nicht 100%, da der Doppelpunkt nach MFN1 im Match enthalten ist (was nicht sein soll):

  • mT❔MFN1: (falsch, der Doppelpunkt am Ende soll nicht enthalten sein)
  • SO❔iPhone (richtig)

Hier kann man damit rumspielen:

Frage: welche Regex matcht die gesuchte Zeichenfolge, aber ignoriert ':' ohne vorangestelltes Fragezeichen?

Gruß

07.02.2019 - 11:34 Uhr

Hallo allerseits,

ich möchte den Hintergrund für alle DataGrids per Style setzen:


<Style TargetType="{x:Type DataGrid}">
    <Setter Property="Background">
        <Setter.Value>
            <VisualBrush Stretch="None">
                <VisualBrush.Visual>
                    <StackPanel>
                        <Label Content="Huhu"/>
                        <Label Content="{Binding Path=TestProperty}" />
                    </StackPanel>
                </VisualBrush.Visual>
            </VisualBrush>
        </Setter.Value>
    </Setter>
</Style>

Ein DataGrid wird dann wie folgt eingebunden (stark verkürzt):

<UserControl>
<!-- ... -->
<DataGrid
        ItemsSource="{Binding ItemListView}"
        SelectedItem="{Binding SelectedItem}">
    <DataGrid.Columns>
        <!-- ... -->
    </DataGrid.Columns>
</DataGrid>
</UserControl>

Das UserControl hat als DataContext ein ViewModel:

public class MyViewModel
{
        public ListCollectionView ItemListView { get; protected set; }

        public object SelectedItem { get; set; }

        public string TestProperty => "Blub";

        // ....
}

Problem: das Label mit dem Text "Huhu" wird angezeigt, das zweite Label bleibt leer. Es scheint, dass das Binding auf TestProperty nicht aufgelöst werden kann. Eine Fehlermeldung wird nicht ausgegeben.

Bindings, welche direkt im XAML des UserControls verwendet werden, funktionieren (ItemListView, SelectedItem).

Wie kann ich im Style dafür sorgen, dass der "richtige" DataContext verwendet wird?

Gruß

11.12.2018 - 08:55 Uhr

Habe mir das mal angesehen. Der Vorteil ist ja, dass ich damit oft benutzte Queries wiederwenden kann.

Durch das Schreiben der Request/ResponseHandler (oder Query oder wie immer man das jetzt nennt) gibt es allerdings auch einen Mehraufwand bei der Implementierung.

Nicht klar ist mir, wie das Konzept UnitOfWork bzw. DbContext zusammen mit dem Mediator verwendet werden kann.

Gruß

10.12.2018 - 15:08 Uhr

Deine UnitOfWork Implementierung würde auch dazu führen, dass Du Repositories instanziierst, die Du gar nicht brauchst.

Moderne Pattern (werfe erneut MediatR in den Raum) machen das alles viel eleganter und übersichtlicher.

Den ersten Punkt verstehe ich nicht - das Repository wird doch nur instanziiert, wenn tatsächlich auf die Property zugegriffen wird:

private AufgabeRepository _aufgabeRepository;
public AufgabeRepository AufgabeRepository
{
    get
    {
        if (_aufgabeRepository == null) _aufgabeRepository = new AufgabeRepository(_context);
        return _aufgabeRepository;
    }
}

Ich gebe zu, dass ich ich das Konzept hinter MediatR & Co. nicht ganz kapiert habe. Gefunden habe ich dieses Beispiel.

public class LoginViewModel : BaseViewModel
{
    private readonly IMediator _mediator;

    // ...
    public IMvxAsyncCommand LoginCommand => new MvxAsyncCommand(LoginAsync, () => CanLogin);

    // ...

    public bool CanLogin => !string.IsNullOrEmpty(Username) && Username.Length >= 5 &&
                                   !string.IsNullOrEmpty(Password) && Password.Length >= 4;
    
    private async Task LoginAsync()
    {
        var result = await _mediator.Send(LoginRequest.WithCredentials(Username, Password))
                                    .ConfigureAwait(false);

        if (result.Succes)
            await NavigationService.Navigate<StartViewModel>()
                                   .ConfigureAwait(false);
        else
            ErrorMessage = result.Message;
    }
}

Klassischerweise würde man das doch so machen:

private async Task LoginAsync()
    {
        bool success = LoginService.Login(Username, Password);

        if (success)
            NavigationService.Navigate<StartViewModel>()
                                   .ConfigureAwait(false);
        else
            ErrorMessage = result.Message;
    }

Wenn ich das richtig verstehe, muss ich bei Verwendung des Mediators für jede Benutzerinteraktion einen Request und Response erstellen. Ist das nicht ein großer Overhead?

Gruß

10.12.2018 - 10:27 Uhr

Danke für die eindeutige Antwort.

Dann stelle ich jetzt doch mal die Frage nach der Implementierung 😉
Ich sehe zwei Möglichkeiten:

  1. direkt den DbContext als UoW zu verwenden
  2. vom DbContext mittels Repositories zu abstrahieren und diese dann in ein eigenes UoW packen:
public class UnitOfWork : IDisposable
{
    protected MyDbContext _context = new MyDbContext();

    private AufgabeRepository _aufgabeRepository;
    public AufgabeRepository AufgabeRepository
    {
        get
        {
            if (_aufgabeRepository == null) _aufgabeRepository = new AufgabeRepository(_context);
            return _aufgabeRepository;
        }
    }

    private PersonRepository _personRepository;
    public PersonRepository PersonRepository
    {
        get
        {
            if (_personRepository == null) _personRepository = new PersonRepository(_context);
            return _personRepository;
        }
    }

    // alle anderen Repositories (eines pro DB-Tabelle)

    public void Commit()
    {
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Variante 1) ist schön einfach. Bei Variante 2) habe ich den Vorteil, dass ich wiederverwendbare Queries im Repository implementieren kann.

Was meint Ihr ...?

Gruß

10.12.2018 - 09:36 Uhr

Nochmals Danke für die Erklärung.

Ich arbeite aktuell an einer Desktopanwendung und kämpfe damit, einen sauberen Bearbeitungszyklus zu implementieren. Das geht über die Ursprungsfrage hinaus, deswegen habe ich dafür mal einen neuen Thread erstellt.

10.12.2018 - 09:33 Uhr

Hallo allerseits,

ich arbeite aktuell an einer Desktop-Anwendung mit WPF und EF6.

In einem vorherigen Beitrag ist die Frage aufgetaucht, wie der Bearbeitungszyklus am besten zu implementieren ist. Damit meine ich:

  1. Benutzer öffnet einen Datensatz zur Bearbeitung
  2. Benutzer nimmt Eingaben vor
  3. Benutzer speichert seine Eingaben

Ich habe es bisher so gemacht, dass ich mit Objekten im detached-State arbeite: erst beim Speichern der Änderungen wird ein neuer DbContext erzeugt und dann gespeichert. Das führt zu Problemen (siehe oben genannter Thread).

Die Alternative ist ja, ein UnitOfWork zu verwenden:

  1. Benutzer öffnet einen Datensatz zur Bearbeitung (Kontext wird erzeugt)
  2. Benutzer nimmt Eingaben vor
  3. Benutzer speichert seine Änderungen (Kontext wird committed).

Wie genau man den UnitOfWork implementiert (DbContext als UoW oder abstrahiert), ist hier erstmal nicht so wichtig.

**Meine Frage: **ist dieses Verfahren auch in einer Desktop-Anwendung zu empfehlen? Was ist hier der Best-Practice? Was, wenn der Benutzer zwischen Schritt 2) und 3) einen Kaffee trinken geht? Dann bleibt der Kontext ja über mehrere Minuten geöffnet ...

Gruß

09.12.2018 - 20:45 Uhr

Das ist der korrekte Weg - auch in dieser Form in der Doku zu finden ... Dank Dependency Injection kann man das auch vollkommen automatisieren

Also erstmal Danke für die Hinweise.

Einige Konzepte sind für mich neu. Ich habe bisher immer mit detached-Objekten gearbeitet (nicht nur in WPF, sondern z.B. auch in Webanwendungen mit JEE).

Ich verstehe Dich so: Der Context wird mittels UnitOfWork erstellt und am Ende des Bearbeitungszyklus comitted.

Nicht ganz klar ist mir, wie Du das mit dem Automatisieren meinst. So wie ich das verstanden habe, kann man per RX dafür sorgen, dass alle Änderungen im ViewModel automatisch auf das Datenobjekt (in meinem Fall Aufgabe) übertragen werden.

Hab ich Dich richtig verstanden?

Gruß

08.12.2018 - 12:16 Uhr

Ein Kontext scoped in der Methode zu erstellen; das war aber auch noch nie eine gute Idee.

Wie würdest Du es denn machen? Am Anfang des Bearbeitungszyklus den Kontext erstellen und auf diesem Kontext arbeiten, bis der Benutzer seine Änderungen gespeichert hat?

Gruß

07.12.2018 - 16:56 Uhr

Hallo,

  1. ein Context sollte von jedem Entity nur ein Exemplar zurückgeben. Wenn du zweimal Anfragen an ein Context machst sollten alle erneut ausgelesen Entities die bereits in der ersten Anfrage geliefert wurden verworfen und statt dessen die bereits ausgegebenen Exemplare erneut geliefert werden. Versuche also die beiden Personen-Instanzen mit denselben Context auszulesen.
  2. Du könntest versuchen IEquatable<TEntity> zu implementieren bei dem du sagst dass zwei Exemplare mit derselben Id identisch sind. Aber sei vorsichtig bei Anfügen von Objekten da hier die Id geändert wird.

Zu 1.):
VonPerson ist immer der aktuell im System angemeldete Benutzer, während AnPerson in der GUI aus einer Liste von Benutzern ausgewählt wurde. Daher sind es immer zwei Instanzen, die aus aus zwei Contexten kommen.

Ich müsste also eine Routine implementieren, welche vor dem Speichern alle NavigationProperties checkt und bei Duplikaten dafür sorgt, dass die betreffenden NavigationProperties auf dieselbe Instanz verweisen.

Zu 2.)
Das habe ich eben mal ausprobiert, der Fehler kommt leider trotzdem. Die Equals()-Methode wird gar nicht aufgerufen.

Nachtrag: Was ich nicht kapiere: EF "weiß" doch, dass Id der Primärschlüssel von Person ist. Da der State von VonPerson und AnPerson explizit auf Unchanged gesetzt wird, sollte es EF doch gar nicht interessieren, dass das zwei unterschiedliche Instanzen sind.

07.12.2018 - 12:22 Uhr

verwendetes Datenbanksystem: SQL Server 2017 Express, EF6

Hallo allerseits,

ich habe eine Datenbank mit ca. 70 Tabellen und verwende EF6 (z.Zt. Database First).

Der relevante Code:


public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Aufgabe
{
    public int Id { get; set; }
    public string Titel { get; set; }
    public int VonPersonId { get; set; }
    public int AnPersonId { get; set; }
    public virtual Person VonPerson { get; set; }
    public virtual Person AnPerson { get; set; }
}

public class AufgabeRepository
{
    // Methoden Get, Exists, Delete, ...

    public void Save(Aufgabe aufgabe)
    {
        using (var context = CreateContext())
        {
            context.Entry(aufgabe).State = System.Data.Entity.EntityState.Added;
            context.Entry(aufgabe.VonPerson).State = System.Data.Entity.EntityState.Unchanged;
            context.Entry(aufgabe.AnPerson).State = System.Data.Entity.EntityState.Unchanged; // FEHLER, wenn AnPerson dieselbe Id wie VonPerson besitzt
            context.SaveChanges();
        }
    }
}

Es geht um den Aufruf Save(): ich übergebe zum Speichern eine Aufgabe im Zustand detached. Da ich nur die Aufgabe speichern will, setze ich den State von VonPerson und AnPerson auf Unchanged.

Mein Problem:
Es ist ein normaler Anwendungsfall, dass der Ersteller der Aufgabe (VonPerson) und der Empfänger (AnPerson) dieselbe Person sind (also dieselbe Id besitzen), aber zwei unterschiedliche Objekt-Instanzen.

Ist das der Fall, erhalte ich bei oben gekennzeichnten Zeile den Fehler:> Fehlermeldung:

System.InvalidOperationException: "Fehler beim Speichern oder Übernehmen der Änderungen, weil mehrere Entitäten des Typs 'Data.Person' den gleichen Primärschlüsselwert aufweisen. Stellen Sie sicher, dass explizit festgelegte Primärschlüsselwerte eindeutig sind.

Workaround:
Diesen Fehler kann ich vermeiden, indem ich für die Referenzen auf Person nur die Id-Werte setze:

using (var context = Entities.CreateContext())
{
    aufgabe.VonPersonId = aufgabe.VonPerson.Id;
    aufgabe.VonPerson = null;
    aufgabe.AnPersonId = aufgabe.AnPerson.Id;
    aufgabe.AnPerson = null;
    context.Entry(aufgabe).State = System.Data.Entity.EntityState.Added;
    context.SaveChanges();
}

Meine Frage:
Ich finde diesen Workaround total umständlich. Das Problem betrifft auch viele andere Entitäten. Ich habe es im Moment so gelöst, dass Per Code-Generator alle Entitäten eine Methode PrepareForSave() haben, welche den Workaournd ausführt.

Geht das nicht einfacher?

Gruß

28.11.2018 - 15:51 Uhr

Dann mach doch ein extra ein-Mann-Projekt.

Ja, mit einem separaten Projekt könnte man es machen. Dann sollten eigentlich nur Entwickler-Lizenzen benötigt werden, wenn diese auch Zugriff auf das Projekt haben.

28.11.2018 - 12:44 Uhr

Mei, jeder will gute Software und keiner ist bereit dafür zu bezahlen.. Wir sprechen hier von 650€ netto (1. Jahr / Standard Lizenz / pro User).

Klar, es sind ja einmalige Kosten, das ist nicht sooo schlimm. Trotzdem find ich's blöd, Lizenzen für Entwickler zu bezahlen, welche die Lizenz gar nicht nutzen.

28.11.2018 - 10:13 Uhr

Ich arbeite nur mit List&Label und bin damit sehr zufrieden. Klar - ist nicht ganz billig, aber es ist das Geld wert.

Die Lizenzbedingungen sind bei List & Label so blöd. Jeder am Projekt beteiligte Entwickler benötigt eine Lizenz - auch wenn er gar nicht am Reporting arbeitet. Ich habe zwei Honorarkräfte, die ab und zu für mich arbeiten. Für die bräuchte ich dann zwei zusätzliche Lizenzen.

28.11.2018 - 09:54 Uhr

Hallo allerseits,

ich weiß es gibt unendlich viele Reporting Tools. Ist klar, dass jede Einschätzung nur einen Ausschnitt aus der "Lösungsmenge" darstellt.

Worum geht's? Ich möchte Anwendern in meiner WPF-Anwendung ermöglichen, eigene Reports zu erstellen. Datenbasis ist eine SQL Server Datenbank.

Einen Report erstellt man i.d.R. in zwei Schritten:

  1. Erstellen eines geeigneten DataSets (z.B. SQL-Query)
  2. Erstellen des Reports-Layouts und die Verbindung von Layout zu DataSet

Beim Erstellen der SQL-Queries kann man den Anwender über vordefinierte Stored Procedures oder einen grafischen Query Editor unterstützen.

Entscheidend ist, dass der Report Designer (also das Layout-Tool) halbwegs benutzerfreundlich ist. Einarbeitung ist natürlich erforderlich.

Ich habe Reports bisher als RDLC Berichte erstellt. Es ist aber für Laien unzumutbar, Visual Studio als Designer zu verwenden.

Interessant bei meiner Recherche fand ich List & Label.

Eine Cloud-Lösung scheidet aus Datenschutzgründen aus.

Habt Ihr mit diesem Thema Erfahrungen? Könnt Ihr einen Report Designer empfehlen?

Gruß 😁

09.10.2018 - 10:57 Uhr

die Modell-Klassen können leicht in eigenes Projekt ausgelagert werden

die Modell-Klassen können leicht angepasst werden, falls mehr als nur POCOs nötig ist (ohne partielle Klassen, etc. verwenden zu müssen wie beim DB-first Ansatz)

in den Modell-Klassen können alle C# Sprachfeatures wie Vererbung (gemeinsame Basisklassen), etc. angewandt werden (bei DB-first nur umständlich möglich)

Ja stimmt, das liegt eigentlich auf der Hand - bei Code First (wie der Name schon sagt) habe ich halt volle Kontrolle über die Datenklassen/POCOs. Das geht bei DB First zwar auch irgendwie (z.B. durch Anpassung der T4-Templates), aber ist halt etwas aufwändiger.

08.10.2018 - 15:13 Uhr

.. das Datenbank-Schema wird separat gepflegt.

Genau das ist nicht der Fall, wenn du Code-First verwendest.

Habe mich unklar ausgedrückt. Wenn man so vorgeht wie von gfoidl beschrieben, wird es eben doch separat gepflegt. Davon bin bei meinem Kommentar ausgegangen.

08.10.2018 - 13:06 Uhr

OK, d.h., Änderungen am DB-Schema können bei Code First entweder per Reverse Engineering oder händisch in den POCOs nachgezogen werden, das Datenbank-Schema wird separat gepflegt.

Dann frage ich mich, wo bei diesem Vorgehen denn der Vorteil von Code-First gegenüber Database First liegt? Abgesehen davon, dass es bei Verwendung von EF Core eh keine Alternative gibt.

07.10.2018 - 11:52 Uhr

verwendetes Datenbanksystem: SQL Server, EF6

Hallo allerseits,

ich weiß, es gibt diverse Artikel zu diesem Thema im Netz. Trotzdem würden mich mal Eure Meinungen zu dieser Frage interessieren.

Ich persönlich bevorzuge Database First, da man hier volle Kontrolle über das Datenbank-Schema hat.

Gleichzeitig weiß ich, dass bei EF Core nur noch Code First unterstützt wird.

Bauchschmerzen bzgl. Code First habe ich vor allem, weil ich - nach meinem Verständnis von Code First - die Kontrolle über das Datenbank-Schema komplett abgebe.

Indexe, Constraints, etc., werden doch "normalerweise" in einem SQL-Skript definiert. Ich kann mir schwer vorstellen, dass ein reales Projekt mit 100 oder mehr Tabellen mit Code First umsetzbar ist. Ich benötige doch zumindest ein SQL-Skript, in dem das Datenbank-Schema definiert ist. Schon alleine zur Dokumentation. Ebenfalls benötigt ja fast jede Applikation initiale Daten, die ja auch per SQL-Skript eingespielt werden. Wie soll das bei Code First gehen?

Gruß

05.10.2018 - 12:10 Uhr

... und wieder was gelernt 😁

04.10.2018 - 18:53 Uhr

Hallo nochmal,

für eine WPF-Applikation wird ein RichText-Editor benötigt, um damit Briefe und E-Mails zu schreiben. Die Briefe am besten als Docx (zur Not RTF), E-Mails im HTML-Format. Der Editor sollte grundlegende Formatierungsfunktionen enthalten und es sollte auch möglich sein, Tabellen und Bilder einzufügen. Der Brief wird aus einer Vorlage heraus erstellt, wobei diverse Text-Variablen (z.B. für die Adresse des Empfängers) von der Applikation vorausgefüllt werden.

Ich habe das zunächst so gelöst, dass ich Word per Interop einbinde. Will der Anwender einen Brief schreiben, öffnet sich Word und die Textvariablen werden von der Applikation ersetzt. Das geht soweit, aber es ist langsam und die Steuerung von Word per Interop ist fehleranfällig.

Jetzt suche ich nach einer Alternative. Idealerweise sollte der Editor direkt in die WPF-App eingebunden werden können.

Für so etwas gibt es kommerzielle WPF-Komponenten, z.B. DevExpress oder Telerik WPF. Ist auf jeden Fall eine Möglichkeit, wobei mich abschreckt, dass die Lizenzgebühren pro Entwickler erhoben werden.

Im kostenlosen/Opensource-Bereich fällt mir eigentlich nur OpenOffice ein. Wobei ich da nicht weiß, wie man das in eine C#/WPF-Applikation einbinden kann.

Es gibt auch Opensource WPF-Editoren, z.B. diesen hier, die möchte ich aber keinem Anwender zumuten.

Jetzt würde mich mal interessieren: habt ihr schon mal vor so einer Anforderung gestanden bzw. wie würdet ihr das "Problem" lösen? Einen Text-Editor selber zu entwickeln ist keine Option, das ist schlicht zu aufwändig.

Gruß

04.10.2018 - 18:30 Uhr

gfoidl meint wenn die aufzurufende Klasse eine asynchrone Schnittstelle aufweist kannst du diese mit await konsumieren und musst nicht Task.Run verwenden.

Dann müsste man aber GetItems selbst als asynchron definieren oder?

public abstract Task<IEnumerable<T>> GetItemsAsync();
03.10.2018 - 13:32 Uhr

Super, besten Dank für die Erklärung. Der Dispatcher sollte also nur für Aufgaben verwendet werden, welche unmittelbar die GUI betreffen und nicht für sonstige Aufgaben (Daten aus der Datenbank laden, etc.).

Da GetItems() abstrakt und somit die Dauer unbekannt ist, werde ich weiterhin das Task.Run() verwenden.

Gruß

03.10.2018 - 09:16 Uhr

Hallo allerseits,

in einem ViewModel möchte ich die Möglichkeit haben, bestimmte Eigenschaften asynchron zu aktualisieren.

Hier ein Beispiel eines ViewModels für ein DataGrid (der Code ist aufs Wesentliche reduziert). Die Methode RefreshAsync aktualisiert die Liste der Elemente, welche mittels Binding im DataGrid angezeigt werden.

namespace MyApp.ViewModel
{
    public abstract class ListViewModelBase<T> : ViewModelBase
    {
        public RangeEnabledObservableCollection<T> ItemList { get; protected set; }

        public ListViewModelBase()
        {
             ItemList = new RangeEnabledObservableCollection<T>();
        }

        public async virtual Task RefreshAsync()
        {
            IEnumerable<T> items = await Task.Run(() => GetItems());
            ItemList.Replace(items);
        }

        public abstract IEnumerable<T> GetItems();
    }
}

Jetzt frage ich mich: ich könnte die Zeile

IEnumerable<T> items = await Task.Run(() => GetItems());

durch diese ersetzen:

IEnumerable<T> items = await Application.Current.Dispatcher.InvokeAsync(() => GetItems());

Meine Frage: wäre es hier besser, statt Task.Run() den Dispatcher-Thread zu verwenden, da ja hier letztlich die GUI aktualisiert wird?

Gruß

19.09.2018 - 12:05 Uhr

Ich hab's hingekriegt 😄, Lösung habe ich hier gefunden: https://wpf.2000things.com/2011/08/30/375-binding-something-in-a-tooltip-to-a-property-on-the-parent-control/

Das Geheimnis ist: man muss den DataContext des Tooltips auf das Element setzen, welches den Tooltip anzeigt und die Fehlermeldung enthält (also in meinen Fall die TextBox). Das gesuchte Element ist der PlacementTarget des Tooltips. Die Bindings im Template gehen dann alle auf diesen DataContext.
Das Ganze sieht dann so aus:

<Style x:Key="ValidatingControlStyle" TargetType="{x:Type FrameworkElement}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip">
                    <Setter.Value>
                        <ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
                            <ToolTip.Template>
                                <ControlTemplate TargetType="ToolTip">
                                    <Border BorderBrush="Blue" BorderThickness="1"
                                            Background="AliceBlue"
                                            CornerRadius="5">
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent}"
                                                       Margin="10"
                                                       Width="150"
                                                       TextWrapping="Wrap"/>
                                        </StackPanel>
                                    </Border>
                                </ControlTemplate>
                            </ToolTip.Template>
                        </ToolTip>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
</Style>
17.09.2018 - 13:12 Uhr

Sorry, hatte ein paar andere Dinge zu tun, komme erst jetzt dazu.

Also, versuche ich folgendes:

<Style x:Key="ValidatingControlStyle" TargetType="{x:Type FrameworkElement}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip.Template">
                    <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ToolTip}">
                                <Border BorderBrush="Blue" BorderThickness="1"
                                            Background="AliceBlue"
                                            CornerRadius="5">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="{TemplateBinding Content}"
                                                       Margin="10"
                                                       Width="150"
                                                       TextWrapping="Wrap"/>
                                    </StackPanel>
                                </Border>
                            </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="ToolTip.Content" Value="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" />
            </Trigger>
        </Style.Triggers>
/Style>

... erhalte ich beim Öffnen des zugehörigen Dialogs zur Laufzeit einen Fehler, den ich nicht recht verstehe:> Fehlermeldung:

TargetType 'ToolTip'" für "ControlTemplate" stimmt nicht mit dem als Vorlage verwendeten Typ "TextBox" überein

Das Element, bei dem die Validierungsnachricht angezeigt wird, ist vom Typ TextBox. Versucht die Rendering-Engine, das Template auf die TextBox anwenden ...?

Naja, ich werds weiter versuchen ...

14.09.2018 - 17:30 Uhr

Hallo allerseits,

ich möchte gerne Validierungsfehler in einem Tooltip anzeigen. Hierzu verwende ich einen Style.

Mache ich das wie folgt, gibt es keine Probleme - der Tooltip wird mitsamt Fehlermeldung angezeigt:


<Style x:Key="ValidatingControlStyle" TargetType="{x:Type FrameworkElement}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
            </Trigger>
        </Style.Triggers>
</Style>

Jetzt möchte ich gerne das Aussehen des Tooltips ändern. Hierzu verwende ich ein Template. Der Tooltip wird mit dem Template auch korrekt gerendert, nur wird die Fehlermeldung nicht mehr angezeigt. Der Tooltip ist leer.


<Style x:Key="ValidatingControlStyle" TargetType="{x:Type FrameworkElement}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip">
                    <Setter.Value>
                        <ToolTip Content="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"> <!-- Text wird nicht angezeigt -->
<!--                    <ToolTip Content="Das ist ein Text"> Text wird angezeigt -->
                            <ToolTip.Template>
                                <ControlTemplate TargetType="ToolTip">
                                    <Border BorderBrush="Blue" BorderThickness="1" 
                                            Background="AliceBlue"
                                            CornerRadius="5">
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{TemplateBinding Content}"
                                                       Margin="10"
                                                       Width="150" 
                                                       TextWrapping="Wrap"/>
                                        </StackPanel>
                                    </Border>
                                </ControlTemplate>
                            </ToolTip.Template>
                        </ToolTip>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
</Style>

Die entscheidende Stelle ist

<ToolTip Content="{Binding Path=(Validation.Errors)[0].ErrorContent}">

Schreibe ich in die Content-Property irgendeinen beliebigen Text rein (siehe Kommentar im obigen XML), wird dieser angezeigt.

Hat jemand eine Idee, wie man das Tooltip dazu bringen kann, die Validation-Message anzuzeigen?

Gruß 🙂

22.08.2018 - 11:36 Uhr

Wenn dein Client ausschließlich auf Windows läuft - was spräche dagegen den initialen "Dominostein" von Windows sichern zu lassen?

Hatte ich auch schon überlegt. Man könnte sogar die Dateiverschlüsselung von Windows verwenden, um den Schlüssel zu sichern. Die Anwendung läuft ja mit den User-Rechten und kann den Schlüssel lesen. Oder aber man verwendet die Data Protection API.

22.08.2018 - 11:15 Uhr

Also z.B. die Passwörter in eine eigene Datenbank packen auf die nur eine API-Zugriff hat deren einzige Aufgabe es ist diese Passwörter abzulegen und bereitzustellen.

Hier muss eine Authentifizierung gegenüber der API stattfinden. Es wird somit auch hier ein Token auf jedem Client-Rechner benötigt, der auch wieder gesichert werden muss ...

Es gibt im Netz zigtausend Tutorials und fertige System, die man 1:1 in sein Projekt implementieren kann.

Ich habe da leider nichts fertiges gefunden. Daher der Ansatz mit symmetrischer Verschlüsselung + aspnet_regiis.. Aber Du hast ja Recht, ich bin ja nicht der erste mit diesem Problem. Da muss es doch Lösungen geben.

21.08.2018 - 16:36 Uhr

Für sowas gibt's eigentlich asymmetrische Verschlüsselungen.

Kannst Du das etwas näher erläutern? Wo liegt der Vorteil gegenüber symmetrischer Verschlüsselung, bezogen auf das von mir beschriebene Setting?

21.08.2018 - 12:41 Uhr

Man könnte das auch wie bei Passwortmanagern machen.
Die Anwendung speichert die verschiedenen Zugangsdaten und der Schlüssel für alles ist ein gemeinsames Passwort.
Dadurch müsste sich der Benutzer zwar immer noch ein Passwort merken, allerdings nur ein einziges.

Wenn die diversen Zugangsdaten mit einem Benutzerpasswort verschlüsselt werden, muss die Anwendung dieses Master-Passwort "kennen". Damit sie ohne User-Interaktion die Zugangsdaten entschlüsseln kann. Damit wären wir beim Ausgangspunkt meiner Frage: wie sichere ich das Master-Passwort?

21.08.2018 - 11:24 Uhr

Hallo,

ich speichere in einer Desktop-Anwendung Passwörter, die von der Anwendung ohne Zutun des Users lesbar sein müssen. Anwendungsfall: die Anwendung ruft im Hintergrund regelmäßig neue E-Mails des Users ab und benötigt Zugriff auf seinen E-Mail-Account. Es gibt noch weitere Anwendungsfälle, betrifft nicht nur E-Mail.

Die Anwendung läuft im Intranet (plus Remote Desktop über VPN), besteht aus mehreren Clients und einem Server. Auf letzterem läuft die Datenbank.

Jetzt überlege ich, wie ich das Ganze am besten realisiere.

Mein Ansatz ist folgender: Ich verwende einen symmetrischen Schlüssel, um die Passwörter zu ver- und entschlüsseln. Den Schlüssel lege ich in der App.config ab und schütze den entsprechenden Teil mit aspnet_regiis. Der symmetrische Schlüssel befindet sich dann auf jedem Client-Rechner, geschützt durch den Mechanismus von aspnet_regiis.

Problemstellung ist also: die User speichern Passwörter Ihrer Web-Accounts (E-Mail, etc.). Die Anwendung muss diese Passwörter 1) sicher in der Datenbank speichern und 2) diese entschlüsseln können, um selbstständig auf die Accounts zugreifen zu können.

Wie würdet ihr das realisieren bzw. habt ihr so etwas schon mal umgesetzt?

Gruß

27.07.2018 - 09:06 Uhr

Das Teil verhält sich sehr merkwürdig, ich kann vorerst mit dem von mir beschriebenen Fix leben.

Danke für die Mühen! 😁

25.07.2018 - 09:24 Uhr

Kannst du ein abgespecktes Projekt zufällig zur Verfügung stellen?

Gerne, hier.

Du musst das Projekt AutoCompleteTest starten. Hierbei sieht man, wie die Reihenfolge der Initialisierung im View (DokumentBriefEditView) das Aussehen bei Validierungsfehlern beeinflusst.

24.07.2018 - 08:15 Uhr

Ja, die Ursache ist nach wie vor unklar. Es ist je nach Dialog (= UserControl) unterschiedlich.

Durch den Hinweis mit dem minimalen Testprojekt bin ich darauf gekommen, dass bei manchen UserControls die Reihenfolge im Konstruktor entscheidend ist. Es musste erst 1) InitializeComponent() aufgerufen wird und dann 2) der DataContext gesetzt werden. Das war bei mir teils andersherum.

public partial class AufgabeEditView : UserControl
{
    public AufgabeEditView(AufgabeEditViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }
}

Aber ich habe auch UserControls, bei denen das nichts geholfen hat.

23.07.2018 - 11:06 Uhr

Ich habe es hinbekommen.

Es folgt eine Beschreibung, falls auch andere diese Problem haben sollten.

Durch die Live-Baumansicht habe ich gesehen, dass in manchen Fällen von WPF ein Adorner über die AutoComplete-Control "gelegt" wurde, obwohl bereits im Template des AutoComplete-Controls ein roten Rahmen für Validierungsfehler konfiguriert ist. Dadurch wurden zwei Rahmen angezeigt, was im Ergebnis aussieht, als ob es ein dicker Rahmen wäre.

Der erste Rahmen kommt aus dem Template des AutoCompleteBox-Control und befindet sich auf Ebene des AutoComplete-Controls. Der zweite liegt auf Ebene des Windows und wird von WPF automatisch angezeigt.

Die Lösung besteht darin, den zweiten auszuschalten, in dem man das ErrorTemplate des AutoComplete mit einem leeren Template setzt:

<toolkit:AutoCompleteBox
    ItemsSource="{Binding VonPersonAdresseList}"
    SelectedItem="{Binding VonPersonAdresse, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
    ValueMemberPath="Description">

   <!-- Sicherstellen, dass kein zweiter Rahmen bei Validierungsfehlern angezeigt wird -->
   <Validation.ErrorTemplate>
       <ControlTemplate />
   </Validation.ErrorTemplate>
</toolkit:AutoCompleteBox>

Warum das ganze nur manchmal vorkommt, ist mir allerdings nicht klar. Naja, jetzt geht's 😁

19.07.2018 - 17:56 Uhr

Validierung läuft erfolgreich durch. Nur der rote Rahmen bleibt.

Einen fundamentalen Unterschied gibt es zwischen den Dialogen nicht. Ich schaue morgen noch mal, vielleicht habe ich irgendeinen signifikanten Unterschied übersehen ...

19.07.2018 - 16:40 Uhr

Hallo allerseits,

ich habe ein Problem, bei dem ich irgendwie nicht weiterkomme.

Ich verwende in einigen Dialogen eine AutoCompleteBox-Komponente vom WPF-Toolkit. Hier ein Beispiel:

<toolkit:AutoCompleteBox 
    ItemsSource="{Binding VonPersonAdresseList}" 
    SelectedItem="{Binding VonPersonAdresse, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" 
    ValueMemberPath="Description" />

Validierung erfolgt mittels INotifyDataErrorInfo. Bei Fehlern wird ein Tooltip angezeigt, (den roten Rahmen kriegt man ja "gratis" dazu):

<Style x:Key="ValidatingControlStyle" TargetType="{x:Type FrameworkElement}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Weitere Styles oder Templates werden nicht eingesetzt.

Mein Problem:

  • Jeder Dialog wird bei Initialisierung validiert.
  • Bei Pflichtfeldern führt das dazu, dass initial der rote Rahmen angezeigt wird, wenn das Feld zu Beginn nicht gefüllt ist.
  • Nun wird der rote Rahmen bei manchen Dialogen besonders dick gezeichnet, bei anderen nicht. Ersteres führt dazu, dass der rote Rahmen nach erfolgreiche Validierung nicht verschwindet.

Ich habe keine Ahnung, woran das liegen kann. Hat jemand von Euch eine Idee, was die Ursache dafür sein könnte?

Gruß

22.06.2018 - 17:58 Uhr

Nachtrag: mich würde noch interessieren, wie die Daten aus dem ViewModel am besten an die Datenzugriffsschicht übergeben werden ...

Die ganzen Tutorials zum Thema MVVM/EF sind meist extrem einfach gehalten, kennt da jemand vielleicht ein Real-World-Beispiel?

EDIT: nachdem ich das geschrieben habe, hab ich noch mal gesucht und tatsächlich ein gutes Tutorial gefunden: https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/

22.06.2018 - 17:50 Uhr

Statische Methoden sind weder modular noch testbar.

Ja, das steht noch auf meiner Liste ...

Ansonsten gilt auch hier

>

Die drei Schichten sind in dem Beispiel korrekt abgebildet, meine ich, oder? Bei MVVM übernimmt das ViewModel ja Aufgaben des Controllers (sprich: die Verbindung zwischen View und Datenmodell).

Ansonsten danke für die Hinweise zum Naming. Werde das berücksichtigen.

22.06.2018 - 13:09 Uhr

Hallo,

ich möchte gerne mal zur Diskussion stellen, wie die Logik zum Anlegen/Bearbeiten von Datensätzen in einer klassischen Datenbank-Anwendung idealerweise implementiert wird.

Technologien: WPF und Entity Framework.

Hierzu ein Mini-Beispiel, um Email-Adressen in der DB anzulegen/bearbeiten. Jede E-Mail-Adresse besitzt zwei Felder:

  • Die eigentliche Adresse
  • Einen optionalen Typ, z.B. Privat, Arbeit, ...

Ich habe es so umgesetzt, dass mit Detached-Objekten arbeite, die

  • an ein ViewModel zur Bearbeitung übergeben werden
  • vom Benutzer editiert werden
  • dann zum Speichern an einen Service.

Wie würdet Ihr das machen?

Gruß

View:


<UserControl x:Class="MyApp.View.EmailEditView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             Height="Auto" Width="400" x:Name="View">
    <DockPanel>         
        <Grid DockPanel.Dock="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <!-- Email-Adresse -->
            <Label Grid.Row="0" Grid.Column="0" Content="Email-Adresse"/>
            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Adresse, UpdateSourceTrigger=LostFocus, ValidatesOnNotifyDataErrors=True}" />
            
            <!-- Email-Typ -->
            <Label Grid.Row="1" Grid.Column="0" Content="Typ"/>
            <ComboBox
                Grid.Row="1" Grid.Column="1" 
                ItemsSource="{Binding EmailTypList}" 
                SelectedItem="{Binding EmailTyp, Mode=TwoWay}" 
                DisplayMemberPath="Name" />
        </Grid>

        <!-- Speichern/Abbrechen -->
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Content="Speichern" Command="{Binding Save}" CommandParameter="{Binding ElementName=View}" />
            <Button x:Name="BtnCancel" Content="Abbrechen" Click="BtnCancel_Click" />
        </StackPanel>
    </DockPanel>
</UserControl>

ViewModel:


namespace MyApp.ViewModel
{
    public class EmailEditViewModel : ValidatingViewModelBase
    {
        private MyApp.Entities.Email email;
	
        private string adresse;
        public string Adresse
        {
            get { return adresse; }
            set { adresse = value; OnPropertyChanged(); }
        }

        private MyApp.Entities.EmailTyp emailTyp;
        public MyApp.Entities.EmailTyp EmailTyp
        {
            get { return emailTyp; }
            set { emailTyp = value; OnPropertyChanged(); }
        }

        private List<MyApp.Entities.EmailTyp> emailTypList;
        public List<MyApp.Entities.EmailTyp> EmailTypList
        {
            get { return emailTypList; }
            set { emailTypList = value; OnPropertyChanged(); }
        }
        
        public ICommand Save { get; set; }

        public EmailEditViewModel(MyApp.Entities.Email email)
        {
            this.email = email;
			
            Adresse = email.Adresse;
            EmailTyp = email.EmailTyp;

            // Liste der Typen, z.B. Arbeit, Privat, Sonstige, ...
            EmailTypList = EmailTypService.GetAll();

            Save = new RelayCommand(SaveExecute, IsValid);
            
            RegisterValidator(() => Adresse, ValidateAdresse);
            ValidateAll();
        }

        protected async Task<string> ValidateAdresse()
        {
            return await EmailValidationRule.ValidateEmail(adresse);
        }

        protected override void UpdateDataModel()
        {
            email.EmailTyp = emailTyp;
            email.Adresse = adresse;
        }

        protected override void SaveExecute(object obj)
        {
            UpdateDataModel();
            EmailService.Save(email);
            Window.GetWindow(obj as DependencyObject).Close();
        }
    }
}

Service + Entitäten:


namespace MyApp.Service
{
    public class EmailService
    {
        public static void Save(MyApp.Entities.Email email)
        {
            using (var context = new MyAppEntities())
            {
                if (email.EmailTyp != null)
                {
                    if (email.EmailTyp.Id > 0)
                    {
                        // Nur den FK ohne referenziertes Objekt verwenden, um keine Duplikate beim Speichern zu erzeugen
                        email.EmailTypId = email.EmailTyp.Id;
                        email.EmailTyp = null;
                    }
                    else
                    {
                        context.Entry(email.EmailTyp).State = EntityState.Added;
                    }
                }

                context.Entry(email).State = email.Id == 0 ? EntityState.Added : EntityState.Modified;
                context.SaveChanges();
            }
        }
    }
}

namespace MyApp.Entities
{
    public partial class Email
    {
        public int Id { get; set; }
        public Nullable<int> EmailTypId { get; set; }
        
        public virtual EmailTyp EmailTyp { get; set; }
    }
    
    public partial class EmailTyp
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}