Laden...

Forenbeiträge von BlonderHans Ingesamt 126 Beiträge

30.03.2024 - 06:06 Uhr

Also ich habe mal die Standard-ConsoleApp mit folgenden dotnet Befehlen auf Windows, macOS und Ubuntu erzeugen lassen:

dotnet publish ConsoleApp -r osx-x64 --sc true -o ./pub/osx-x64 -p:PublishSingleFile=true
dotnet publish ConsoleApp -r win-x64 --sc true -o ./pub/win-x64 -p:PublishSingleFile=true

und dabei kam folgendes heraus

lauffähig Windows macOS Ubuntu
osx-x64 NEIN ja ja
win-x64 ja ja ja

Verwendete Solution im Anhang

28.02.2024 - 10:59 Uhr

Wenn dir OpenXML zu sperrig ist kannst du es auch mit ClosedXml versuchen. Das arbeitet im Hintergrund mit OpenXml, hat aber den Anspruch etwas freundlicher im Umgang zu sein.

17.02.2024 - 18:56 Uhr

Ich frage mich, wie du das sehen willst, denn du lässt die Form wo das passiert gar nicht anzeigen.

PS: Verwende für Code doch bitte die Code-Tags.

09.02.2024 - 10:25 Uhr

Zitat von CSharpNewbie2022

  1. Bei diesem Schichten Modell trennen wir GUI, Business Logic and Data Access. Das habe ich so adaptiert. Als Data Access wäre der Zugriff zur Datenbank und jegliche Art von Lesen und Beschreiben von Dateien. Aber wie sieht es ..
    1. mit Schnittstellen (Ethernet, Ethercat, SPI, RS232, ...)
    2. mit Webdiensten also Kommunikation zu Api s aus, gehören diese Elemente auch in den Data Access Layer?

Diese Dinger unterscheiden sich zu DB und Dateien Zugriff darin, dass mit einem externen Partner kommuniziert wird, also gehört das nun zur Business Logic oder zu dem Data Access Layer?

Alles was nicht in deiner Anwendung ist, ist außen/extern und du baust dir für deine Anwendung eine Schnittstelle um mit dieser Außenwelt zu kommunizieren. Du darfst das gerne unter dem Begriff Infrastructure zusammenfassen.

09.02.2024 - 10:17 Uhr

Zitat von CSharpNewbie2022

2. Ich nutze den DI Container von Microsoft für alle ViewModels und diverse Singleton Klassen aus der Business Logic, damit diese in der GUI Schicht zur Verfügung stehen. Aber kleine Klassen, die nur innerhalb einer Klasse verwendet werden, erstelle ich immernoch mit new. Ist das eher gegen das Konzept? Sollte jedes Object mit dem DI erstellt werden?

Mal eine vereinfachte Darstellung:

  • Alle Klassen, die für andere Klassen Abhängigkeiten (Dependencies) darstellen sowie die, die eben solche Abhängigkeiten (Dependencies) haben werden registriert und über den DI-Container erzeugt.
  • Die anderen Klassen ... wozu?

Das ViewModel ist abhängig von einem DatenService und dieser ist abhängig von einem DbContext also werden diese alle am DI-Container registriert und von diesem erzeugt.

Das ViewModel schickt jetzt einen Datensatz zum speichern an den DatenService und der schickt das an den DbContext weiter. Dieser Datensatz (ist eine Klasse) hat keinerlei Abhängigkeiten (Dependencies) zu irgendwas und hat somit nichts mit Dependency-Injection am Hut.

02.02.2024 - 06:34 Uhr

Zitat von Stefan68

Eine frage zum Thema MVP ist es in der Praxis üblich das im Model auch Forms-Objekte wie z.B. ListViewItems oder andere Forms-Controls erzeugt werden und an die Form übergeben werden?

Hier wird das gut beschrieben Model-View-Presenter in WinForms

25.01.2024 - 20:45 Uhr

Aber über OnKeyUp/OnKeyDown bekommst du doch alles was du benötigst. So wie ich das sehe werden diese Steuerzeichen übermittelt, als wenn du die über die Tastatur eingeben würdest (so hat man früher zu DOS Zeiten die Sonderzeichen bekommen):

  • ALT down
  • 0 down
  • 0 up
  • 0 down
  • 0 up
  • 3 down
  • 3 up
  • 0 down
  • ALT up
  • 0 up

Damit wird RS übermittelt. Die anderen Zeichen analog. Damit solltest du jetzt deinen Barcode zusammenstellen können.

24.01.2024 - 21:08 Uhr

Zitat von Abt

Persönlich lösche ich aber auch immer diese Collection und fülle sie dann.

Die generelle Empfehlung ist in WPF, genau das eigentlich nicht zu tun, weil der Overhead von OnPropertyChanged,der Allocation und die Bindungsaktualisierung i.d.R. teurer ist als das Clear, das O(n) ist.

Er meinte damit "lösche den Inhalt" - sieht man auch wenn man seinen Code dazu anschaut 😉 Also genau so wie empfohlen.

24.01.2024 - 20:56 Uhr

Zitat von Abt

OnPropertyChanged ist der Framework-Mechanismus, wenn Du Properties hast, die einen "Dirty State" haben, der eigentlich optimalerweise zu vermeiden ist. Damit triggerst Du WPF an, dass die Referenz neu bekannt gemacht wird und die Value wird aktualisiert.

Er lässt sich bei den Typen vermeiden, die eine direkte Möglichkeit der Bindung haben; wie die ObservableCollection.

Diese Aussage verstehe ich nicht, denn ich hatte noch niemals Probleme damit so eine ObservableCollection neu zu erstellen.

Doch hattest Du, denn die Bindung in diesem Moment ist nicht mehr vorhanden, sie zeigt auf die alte Referenz.
Du wirst sicherlich eben auch OnPropertyChanged triggern, um das zu lösen.

Also ich habe speziell diese "Technische Limitation" nicht verstanden, da es einfach nur eine observable Property braucht wie halt bei jeder Property, deren Wert sich ändern kann und diese Änderungs Benachrichtigung benötigt wird.

Hier von einem Problem zu sprechen?

Ich kann versichern, dass ich da genau kein Problem hatte/habe/haben werde.

24.01.2024 - 13:17 Uhr

Zitat von Abt

Technische Limitation: Deine ObservableCollection darf nicht neu erzeugt werden.

Diese Aussage verstehe ich nicht, denn ich hatte noch niemals Probleme damit so eine ObservableCollection neu zu erstellen.

Kannst du das bitte einmal erläutern?

22.01.2024 - 13:36 Uhr

Du kannst es mit SoundPlayer versuchen.

Der spielt z.B. eine WAV-Datei ab die du beliebig starten und anhalten kannst.

private readonly SoundPlayer _sound = new SoundPlayer("<Pfad zu einer WAV-Datei>");
const int DIT_LENGTH = 100;
const int DAH_LENGHT = DIT_LENGTH * 3;
const int SYMBOL_LENGTH = DIT_LENGTH;
const int CHAR_LENGth = DIT_LENGTH * 3;
const int WORD_LENGTH = DIT_LENGTH * 7;

public async Task SOS()
{
    // S
    await Dit();
    await SymbolGap();
    await Dit();
    await SymbolGap();
    await Dit();
    
    await CharGap();

    // O
    await Dah();
    await SymbolGap();
    await Dah();
    await SymbolGap();
    await Dah();
    
    await CharGap();

    // S
    await Dit();
    await SymbolGap();
    await Dit();
    await SymbolGap();
    await Dit();
}

public async Task Dit()
{
    _sound.Play();
    await Task.Delay(DIT_LENGTH);
    _sound.Play();
}

public async Task Dah()
{
    _sound.Play();
    await Task.Delay(DAH_LENGTH);
    _sound.Play();
}

public Task SymbolGap() => Task.Delay(SYMBOL_LENGTH);
public Task CharGap() => Task.Delay(CHAR_LENGTH);
public Task WordGap() => Task.Delay(WORD_LENGTH);
22.01.2024 - 12:41 Uhr

Die Antwort findest du auf stackoverflow.com.

TL;DR

Is so, liegt aber an Windows selber, dann dotnet leitet das nur an die Window Api weiter und das kommt dabei heraus.

BTW

Es wäre schön, wenn du Code in die entsprechenden Code-Tags packst, dann würde dein Code z.B. so aussehen (hübsch, gell?)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace Beep_Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void CmdEnde_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void CmdStart_Click(object sender, EventArgs e)
        {
            Console.Beep(600, 100); // Länge 50 ms
            Console.Beep(600, 100);
            Console.Beep(600, 100);
            Console.Beep(600, 300); // Länge 150 ms
            Console.Beep(600, 300);
            Console.Beep(600, 300);
            Console.Beep(600, 100); // Länge 50 ms
            Console.Beep(600, 100);
            Console.Beep(600, 100);
        }
    }
}
16.01.2024 - 23:23 Uhr

Wpf.Extensions.Hosting

// Create a builder by specifying the application and main window.
var builder = WpfApplication<App, MainWindow>.CreateBuilder(args);

// Configure dependency injection.
// Injecting MainWindowViewModel into MainWindow.
builder.Services.AddTransient<MainWindowViewModel>();

// Configure the settings.
// Injecting IOptions<MySettings> from appsetting.json.
builder.Services.Configure<MySettings>(builder.Configuration.GetSection("MySettings"));

// Configure logging.
// Using the diagnostic logging library Serilog.
builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) => loggerConfiguration
    .ReadFrom.Configuration(hostingContext.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Debug()
    .WriteTo.File(
        @"Logs\log.txt", 
        rollingInterval: RollingInterval.Day));
    
var app = builder.Build();

await app.RunAsync();
16.01.2024 - 15:16 Uhr

Zitat von Th69

Oder suche mal nach "high precision/resolution timer", z.B. PrecisionTimer.NET.

Das löst das Problem aber auch nicht, es wird evtl. etwas kleiner. Mein Ansatz löst das Problem und macht es sogar noch testbar.

16.01.2024 - 14:24 Uhr

Hier habe ich mal etwas, womit du ein wenig herumspielen kannst.

https://dotnetfiddle.net/QqcXHo

using System;

var startDateTime = new DateTimeOffset( 2021, 01, 01, 00, 00, 00, TimeSpan.Zero );
var endDateTime = startDateTime.AddMinutes( 1 );

var clock = new FakeClock { UtcNow = new DateTimeOffset( 2021, 01, 01, 00, 00, 00, TimeSpan.Zero ) };
var foo = new Foo( clock, TimeSpan.FromMilliseconds( 500 ) )
{
    ActualSpeed = 60,
};
foo.Start();

while ( clock.UtcNow < endDateTime )
{
    var timeSpan = TimeSpan.FromMilliseconds( 500 + Random.Shared.Next( 5, 50 ) );
    clock.UtcNow += timeSpan;
    var slices = foo.Tick();
    Console.WriteLine( "[{0:hh:mm:ss.ffffff}] {1} ({2})", clock.UtcNow, foo.Distance, slices );
}

foo.Stop();


interface ISystemClock
{
    DateTimeOffset UtcNow { get; }
}

class RealTimeClock : ISystemClock
{
    public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}

class FakeClock : ISystemClock
{
    public DateTimeOffset UtcNow { get; set; } = DateTimeOffset.UtcNow;
}

class Foo
{
    private DateTimeOffset _lastTick;
    public Foo( ISystemClock systemClock, TimeSpan slice )
    {
        SystemClock = systemClock;
        Slice = slice;
    }
    public bool IsRunning { get; private set; }
    public double ActualSpeed { get; set; }
    public double Distance { get; private set; }
    public ISystemClock SystemClock { get; }
    public TimeSpan Slice { get; }
    public int Tick()
    {
        if ( !IsRunning ) return 0;
        var now = SystemClock.UtcNow;
        var duration = now - _lastTick;
        if (duration < Slice) return 0;
        var slices = Convert.ToInt32( duration.TotalMilliseconds / Slice.TotalMilliseconds );
        var consumedDuration = slices * Slice;
        _lastTick = now - ( duration - consumedDuration );

        Distance += ActualSpeed / TimeSpan.FromMinutes( 1 ).TotalMilliseconds * slices * Slice.TotalMilliseconds;
        return slices;
    }

    public void Start()
    {
        if ( IsRunning ) return;
        _lastTick = SystemClock.UtcNow;
        IsRunning = true;
    }
    public void Stop()
    {
        if ( !IsRunning ) return;

        IsRunning = false;
    }
}
16.01.2024 - 00:21 Uhr

Das ist ja eine lustige Logik in dem Programm. Auf jeden Fall ist die Userliste immer sehr schön aufgeräumt.

13.01.2024 - 15:50 Uhr

Zitat von DanHue

Ich bin langsam am verzweifeln...

Anscheinend noch nicht genug.

Gibt es vielleicht ein Idee wie man das anders lösen könnte?

Wenn du ernsthaft an einer Lösung interessiert bist dann hättest du schon längst (gleich von Anfang an) ein kleines Demo-Projekt gezeigt anstatt größtmöglichst kompliziert verklausuliert den Ablauf beschrieben bzw. immer nur kleinste Fragmente gezeigt, die aber nicht den gesamten Zusammenhang darstellen. Es gibt viele falsche Wege und auf jeden Fall erheblich weniger richtige Wege. Woher sollen wir wissen, wo genau dein Fehler liegt ohne den gesamten (relevanten) Code zu sehen?

09.01.2024 - 14:03 Uhr

Na endlich.

Und wenn du beim nächsten Mal auch direkt Beispieldaten mitlieferst, dann geht das auch erheblich schneller.

Das Rumgeeiere fing vor 11 Tagen an und zog sich wie ein Kaugummi.

Heute endlich Beispieldaten und nach ein paar Stunden alles geklärt.

09.01.2024 - 09:21 Uhr

Also hier haben wir die Werte der Anschnittscheiben im DataGrid

//                                              Anschnittscheibe
TblProgramminfo.Rows.Add(new object[] { "5075777", "7778888", false, true });
TblProgramminfo.Rows.Add(new object[] { "5075999", "8889999", false, true });

Und diese aus der ComboBox

//                                       SAP-Nummer
TblScheibendaten.Rows.Add(new object[] { "8318555", "250x16x76,2", "Schneid-Formend", "Anschnitt / Fuehren"});
TblScheibendaten.Rows.Add(new object[] { "8318556", "250x16x76,2", "Schneid-Formend", "Anschnitt / Fuehren" });
TblScheibendaten.Rows.Add(new object[] { "8318557", "250x16x76,2", "Schneid-Formend", "Anschnitt / Fuehren" });

Und nach welcher Logik soll da jetzt 7778888 mit welchem der Werte 8318555,8318556,8318557 matchen?

06.01.2024 - 12:46 Uhr

Hier mal dein Beispiel umgesetzt

<Window x:Class="WpfApp.DataWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="DataWindow"
        Width="800"
        Height="450"
        mc:Ignorable="d">
    <Grid>
        <DataGrid x:Name="AutomobileDataGrid"
                  AutoGenerateColumns="True"
                  AutoGeneratingColumn="AutomobileDataGrid_AutoGeneratingColumn" />
    </Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp
{
    /// <summary>
    /// Interaktionslogik für DataWindow.xaml
    /// </summary>
    public partial class DataWindow : Window
    {
        private readonly IList<HerstellerLookup> _herstellerList =
            [
                new HerstellerLookup { Id = 1, Name = "BMW", },
                new HerstellerLookup { Id = 2, Name = "MERCEDES", },
                new HerstellerLookup { Id = 3, Name = "OPEL", },
                new HerstellerLookup { Id = 4, Name = "VW", },
            ];
        private readonly IList<LackierungLookup> _lackierungList =
            [
                new LackierungLookup { Id = 1, Name = "Blau", Zusatz = "Alkyd", Pigmentierung = "leicht", },
                new LackierungLookup { Id = 2, Name = "Grau", Zusatz = "Alkyd", Pigmentierung = "leicht", },
                new LackierungLookup { Id = 3, Name = "Schwarz", Zusatz = "Polyacryl", Pigmentierung = "nein", },
                new LackierungLookup { Id = 4, Name = "Weiss", Zusatz = "Polyacryl", Pigmentierung = "nein", },
                new LackierungLookup { Id = 5, Name = "Silber", Zusatz = "Alkyd", Pigmentierung = "leicht", },
            ];
        private readonly IList<Automobil> _automobilList =
            [
                new Automobil { Id = 1, HerstellerId = 4, Baujahr = 2015, LackfarbeId = 1, Kilometerstand = 50_000, },
                new Automobil { Id = 2, HerstellerId = 1, Baujahr = 2012, LackfarbeId = 2, Kilometerstand = 100_000, },
                new Automobil { Id = 3, HerstellerId = 1, Baujahr = 2019, LackfarbeId = 3, Kilometerstand = 30_000, },
                new Automobil { Id = 4, HerstellerId = 2, Baujahr = 2018, LackfarbeId = 4, Kilometerstand = 40_000, },
                new Automobil { Id = 5, HerstellerId = 3, Baujahr = 2020, LackfarbeId = 5, Kilometerstand = 50_000, },
            ];

        public DataWindow()
        {
            InitializeComponent();

            AutomobileDataGrid.ItemsSource = _automobilList;
        }

        private void AutomobileDataGrid_AutoGeneratingColumn( object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e )
        {
            switch ( e.Column.SortMemberPath )
            {
                case nameof( Automobil.HerstellerId ):
                    e.Column = new DataGridComboBoxColumn
                    {
                        Header = e.Column.Header,
                        ItemsSource = _herstellerList,
                        DisplayMemberPath = nameof( HerstellerLookup.Name ),
                        SelectedValuePath = nameof( HerstellerLookup.Id ),
                        SelectedValueBinding = new Binding( nameof( Automobil.HerstellerId ) ) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, },
                    };
                    break;
                case nameof( Automobil.LackfarbeId ):
                    e.Column = new DataGridComboBoxColumn
                    {
                        Header = e.Column.Header,
                        ItemsSource = _lackierungList,
                        DisplayMemberPath = nameof( LackierungLookup.Name ),
                        SelectedValuePath = nameof( LackierungLookup.Id ),
                        SelectedValueBinding = new Binding( nameof( Automobil.LackfarbeId ) ) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, },
                    };
                    break;
                default:
                    break;
            }
        }
    }

    public class Automobil
    {
        public int? Id { get; set; }
        public int? HerstellerId { get; set; }
        public int? Baujahr { get; set; }
        public int? LackfarbeId { get; set; }
        public int? Kilometerstand { get; set; }
    }

    public class HerstellerLookup
    {
        public int Id { get; init; }
        public string Name { get; init; } = string.Empty;
    }

    public class LackierungLookup
    {
        public int Id { get; init; }
        public string Name { get; init; } = string.Empty;
        public string Zusatz { get; init; } = string.Empty;
        public string Pigmentierung { get; init; } = string.Empty;
    }
}
06.01.2024 - 00:07 Uhr

In der Spalte "Anschnittscheibe" steht der Wert "7778888" und du beklagst dich, dass in der ComboBox nichts angezeigt wird, wenn in der ComboBox.ItemsSource folgende Werte enthalten sind "0815", "0816", "4711" - ist das richtig so?

Dann pack doch mal diese Werte in die Liste für ComboBox.ItemsSource "0815", "0816", "4711", "7778888".

Ist der Wert nicht in der Liste, dann zeigt die ComboBox nichts an (sieht so aus wie in deinem Bild DataGridComboBoxColumn_04.jpg).

05.01.2024 - 19:59 Uhr

So sollte es passen

var dgComboBoxColAnschnittscheibe = new DataGridComboBoxColumn()  
{  
   Header = "Anschnittscheibe",  
   Width = new DataGridLength(150),  
   ItemsSource = new List<string>{ "0815", "0816", "4711" },  
   SelectedValueBinding = new Binding("Anschnittscheibe") { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, },  
   DisplayIndex = 2  
};  
e.Column = dgComboBoxColAnschnittscheibe;  
03.01.2024 - 15:23 Uhr

Zitat von Th69

Zur Info für oehrle: auch bei MVVM in einem ViewModel funktioniert dies (nur eben per Data Binding, anstatt direkt im Code ItemsSource zu setzen).

Genau das funktioniert bei mir nicht. Das ging nur im Code. Setzt man die ItemsSource per DataBinding im XAML dann bleibt die ComboBox leer und Snoop-WPF sagt: „Was für eine ItemsSource?“

03.01.2024 - 14:02 Uhr

Zitat von Th69

Ich verstehe nicht, was du als Ausschlußkriterium siehst.

Du hast tatsächlich Recht. Im Code-Behind im Event gesetzt funktioniert das wie gewünscht.

03.01.2024 - 11:23 Uhr

Zitat von Th69

Ich bin mir sicher, daß es auch mit der DataGridComboBoxColumn funktioniert, es kommt nur auf die passenden Datentypen an.

Diese Annahme wird weder durch die Dokumentation noch Snoop-Wpf gestützt.

03.01.2024 - 09:04 Uhr

Also im Code-Behind der View kommt eigentlich nur Code rein der zur Visualisierung benötigt wird. Der gesamte Rest nimmt Platz im ViewModel, DataServices, Services usw. In der View muss dir ja auch klar sein, von wo du die Daten beziehst und da das abhängig von den Daten ist, ist das auch im ViewModel bekannt.

Bei dem DataTemplate und DataEditTemplate beides Male die ComboBox eintragen, allerdings im DataTemplate mit IsEnabled="False".

03.01.2024 - 03:38 Uhr

Zitat von oehrle

Ne, der definierte Wert von Anschnittscheibe wird nicht angezeigt, bleibt leer. Eigentlich müßte da der Wert 7778888 angzeigt werden.

Wenn da der Wert 7778888 angezeigt werden soll, dann verstehe ich deine Zeichnung mit dem gelben und dem grünen Pfeil nicht.

"SAP-Nummer" soll als Auswahlkriterium in DataGridComboBox auswählbar sein.

Zu sehen sind die Nummern 8318555,8318556,8318557

Ich hätte jetzt vermutet, dass da diese SAP-Nummern stehen sollten.

Egal, so geht das auf jeden Fall nicht, denn das kann die DataGridComboBoxColumn laut Dokumentation nicht.

Um die Dropdownliste aufzufüllen, legen Sie zunächst die ItemsSource -Eigenschaft für die ComboBox fest, indem Sie eine der folgenden Optionen verwenden:

Somit bleibt nur noch die DataGridTemplateColumn übrig um das zu realisieren.

31.12.2023 - 14:01 Uhr

Zitat von GeneVorph

[…] warum da kein Brush drinne ist. […]

Wenn du Colors bemühst und die Eigenschaften vom Typ Color sind (macht ja auch Sinn) wieso erwartest du dann den Typ Brush?

Wenn du das haben möchtest dann nimm statt Colors die Klasse Brushes, denn die hat (wie der Name schon verkündet) Eigenschaften vom Typ Brush (also den du auch haben möchtest).

Wenn man in die Apfelkiste greift und sich wundert dass da keine Bananen drin sind. 🤔

30.12.2023 - 23:04 Uhr

Du weißt aber schon, dass die statischen Eigenschaften der statischenColors Klasse vom Typ Color sind?

Du weißt aber schon, dass die statischen Eigenschaften der statischenBrushes Klasse vom Typ Brush(bzw. konkret SolidColorBrush) sind?

Das casten in eine statische Klasse (z.B. Colors, Brushes) ist idR. immer falsch.

29.12.2023 - 12:32 Uhr

Ja, das Timing spielt halt auch eine große Rolle. Darum solltest du auch mit dem Debugger arbeiten und hier z.B. prüfen, welcher Wert in DataContext steckt. Dann wäre dir auch aufgefallen, dass der zu dem Zeitpunkt ebenfalls null ist.

Wenn du mal beschreiben könntest was du wirklich vorhast - also mehr Details als "ich will Daten mitnehmen" - dann könnte man dir wirklich Beispiel-Code geben.

29.12.2023 - 00:32 Uhr

Nun ja du kannst casten

var viewmodel = DataContext as MyClass;

sowie weitere viele Dinge die aber alles Grundlagen von C# sind

wenn ich MyClass in Window1 in Codebehind lade, das dieser dann leer ist.

Hmmm, es ist auch hilfreich die richtige Terminologie zu verwenden. Wenn du eine neue Instanz erzeugst, dann wird diese eben nicht geladen, sondern erzeugt und ist auch nicht leer sondern enthält die default Werte.

Problematisch ist, das man dann oft aneinander vorbei redet weil man es falsch beschreibt/benennt.

28.12.2023 - 20:57 Uhr

Lass es mich so erklären:

Du hast einen Korb und legst dort einen Apfel hinein. Wenn dein Bruder jetzt in diesen Korb hineinschaut, was sieht er? Genau, diesen einen Apfel.

Jetzt nimmt dein Bruder einen neuen Korb, der deinem Korb erstaunlich ähnlich ist, und schaut in diesen, was sieht er? Genau, gähnende Leere.

Wenn du erklären kannst, warum das so ist, dann hast du dein Problem erklärt.

26.12.2023 - 23:51 Uhr

Dir ist schon bewusst, dass wir keine Möglichkeit haben auf deinen Bildschirm oder deine Festplatte zu schauen?

Warum zeigst du uns nicht eine abgespeckte aber in sich lauffähige Version die (nur) dein Problem zeigt? Es ist sehr ermüdend jeden Popel einzeln aus der Nase heraus zu ziehen nur um festzustellen, dass da noch mehr ist was relevant aber für uns unbekannt ist.

In solch einer abgespeckten Version entfernt man z.B. dieses ganze Dialog und Datei Geraffel und liefert einfach eine statisches oder generiertes Ergebnis zurück.

26.12.2023 - 19:43 Uhr

Du hast da eine Liste UnderlyingList die du leerst und wieder füllst, die aber in der View nicht verwendet wird (oder willst du das nicht zeigen?).

Du hast zwei Eigenschaften Underlying1 und Underlying2 wo es in der View eine Bindung mit der ersten gibt, diese werden aber in der ButtonLoadExecute Methode nicht aktualisiert.

Und nun erwartest du das die View darauf irgendwie reagiert. Warum?

26.12.2023 - 14:34 Uhr
  • Wieso zeigst du uns nicht wie UnderlyingList deklariert ist?
  • Wieso deserialisierst du eine ObservableCollection obwohl du diese niemals als Observable einsetzt? Ein Array hätte hier gereicht.
  • Die Deklaration der Underlying Klasse wäre auch hilfreich, fehlt aber.
  • Du bindest an Underlying1 - was auch immer das sein soll und wie auch immer das gefüllt wird.

Und nur am Rand bemerkt:

Bei der Load Methode vermengst du UI mit Infrastruktur und Präsentation. Besser wäre im ViewModel ein LoadCommand der als CommandParameter die gewählte Datei übergeben bekommt. In der View rufst du den Dialog auf und wenn das erfolgreich war, dann führst du den LoadCommand aus. Und ja, das kommt in das Code-Behind der View und ist nicht schlimm.

22.12.2023 - 09:40 Uhr

Im Anhang habe ich dir ein komplettes WPF-Projekt angehängt.

Allerdings habe ich den Datenzugriff mit einem Fake-DataService vereinfacht. Du kannst dir aber einen eigenen DataService schreiben, der dann auf die Datenbank zugreift.

21.12.2023 - 11:42 Uhr

Ist das AV.RP_NEED, PROD.RP_NEED, MONT.RP_NEED (C# Code) wirklich gleich AV.RP_NEED as AV, PROD.RP_NEED as PROD, MONT.RP_NEED as MONT (SQL-Statement im Management Studio) und wenn nicht, was könnte das für Auswirkungen haben?

20.12.2023 - 21:06 Uhr

Vorab: Es gibt hier Code-Blocks die kann man verwenden, damit der Code zu 3245% lesbarer ist als ohne.

Eine ComboBox ist abgeleitet von einem ItemsControl und dieses hat folgende Eigenschaften die relevant sind:

  • DisplayMemberPath
  • SelectedValuePath
  • SelectedValue

Wenn du im ViewModel eine Liste hast mit den Anreden,

public ICollection<Anreden> Anreden { get∞ }
[Reactive] public int? SelectedAnredeId { get; set; } // hier steht der gewählte Wert

dann kannst du diese wie folgt an die ComboBox binden

<ComboBox ItemsSource="{Binding Anreden}"
          SelectedValue="SelectedAnredeId"
          SelectedValuePath="Anrede_id"
          DisplayMemberPath="Anrede"/>

PS: Hast du bemerkt wie gut man den Code in diesem Beitrag sehen kann? Das machen diese Code-Blocks

19.12.2023 - 09:25 Uhr

Versuch es doch mal mit

<Image Source="/TextEditor;component/Images/bold.png"/>

Also mit einem Slash vor dem Bibliotheks-Namen

18.12.2023 - 06:30 Uhr

Aus diesen Daten

create table RP_PROJECT
(
  ID int not null,
  primary key (ID)
);

create table RP_PROJECT_DATA
(
  RP_PROJECT_ID int not null,
  RP_REIHENFOLGE int not null,
  RP_OPERATION varchar(4) not null,
  RP_NEED float not null
);

insert into RP_PROJECT (ID)
  VALUES
  (251),
  (253),
  (254);

insert into RP_PROJECT_DATA (RP_PROJECT_ID, RP_REIHENFOLGE, RP_OPERATION, RP_NEED) 
  VALUES 
  (251, 1, 'AV', 170.61),
  (251, 2, 'PROD', 457.535619),
  (251, 3, 'MONT', 147.341979),
  (253, 1, 'AV', 52.8),
  (253, 2, 'PROD', 139.2),
  (253, 3, 'MONT', 48),
  (254, 1, 'AV', 38.929);

bekommst du so deine Werte in einer Zeile

select ID RP_PROJECT_ID, AV.RP_NEED AV, PROD.RP_NEED PROD, MONT.RP_NEED MONT
from RP_PROJECT
left join RP_PROJECT_DATA AV on AV.RP_PROJECT_ID=ID and AV.RP_OPERATION='AV'
left join RP_PROJECT_DATA PROD on PROD.RP_PROJECT_ID=ID and PROD.RP_OPERATION='PROD'
left join RP_PROJECT_DATA MONT on MONT.RP_PROJECT_ID=ID and MONT.RP_OPERATION='MONT'

Live-Beispiel SQL Fiddle

13.12.2023 - 12:33 Uhr

Mein Beispiel-Projekt setzt voll auf DI und jedes Window wird mittels einem DI-Container erzeugt und der sorgt dafür, dass eben diese Abhängigkeiten (Dependencies) im Konstruktor automatisch übergeben (injected) werden.

Verantwortlich dafür ist Wpf.Extensions.Hosting . Verkabelt wird das in der Program.cs

// Create a builder by specifying the application and main window.
using WpfApp;

var builder = WpfApplication<App, MainWindow>.CreateBuilder( args );

// Configure dependency injection.
// Injecting MainWindowViewModel into MainWindow.
builder.Services
    .AddTransient<MainWindowViewModel>()
    .AddTransient<PersonViewModel>()
    .AddTransient<AnimalViewModel>()
    .AddTransient<CombinedViewModel>()
    ;

var app = builder.Build();

await app.RunAsync();

In WPF kann man für einen Typ ein DataTemplate festlegen und genau das habe ich in der App.xaml gemacht, denn dort angelegt gilt dass dann für die gesamte Anwendung.

        <DataTemplate DataType="{x:Type viewmodels:CombinedViewModel}">
            <views:CombinedControl />
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewmodels:AnimalViewModel}">
            <StackPanel>
                <DockPanel>
                    <Label Content="Species:"
                           DockPanel.Dock="Top" />
                    <TextBox Text="{Binding Species, UpdateSourceTrigger=PropertyChanged}" />
                </DockPanel>
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewmodels:PersonViewModel}">
            <StackPanel>
                <DockPanel>
                    <Label Content="Name:"
                           DockPanel.Dock="Top" />
                    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
                </DockPanel>
            </StackPanel>
        </DataTemplate>

Ja, in der Beispiel-Anwendung steckt eine ganze Menge an (praktischen) Features. Mir ist es aber auch wichtig zu zeigen, wie diese Features zusammenarbeiten - auch wenn es am Anfang einen erschlagen könnte.

12.12.2023 - 21:45 Uhr

Wenn Person und Animal im Combined als Eigenschaft vorhanden sind, dann braucht man die eigentlich nicht nochmal im MainViewModel. Und wenn du mit einem DI-Container arbeitest, dann brauchst du auch keine Instanzen erzeugen, sondern übergibst die automatisch.

namespace Fictiv.ViewModel
{
  public class MainViewModel
  {
    public ViewModelCombine Combine { get; }

    public MainViewModel( ViewModelCombine combine ) // per DI übergeben
    {
      Combine = combine;
    }
  }
  
  public class ViewModelCombine : ObservableObject
  {
    // the viewmodel have to know the model.
    private readonly Model.Combine CombineModel;

    public ViewModelPerson Person { get; }
    public ViewModelAnimal Animal { get; }

    private string combineCombinedText;
    public string Bind_combineCombinedText
    {
          get { return combineCombinedText; }
          set { SetProperty(ref combineCombinedText, value); }
    }

    // button to combine person with animal text
    public ICommand Bind_btnCombineCommand { get; }

    // Constructor
    public ViewModelCombine(ViewModelPerson person, ViewModelAnimal animal)
    {
          Bind_combineCombinedText = "EMPTY";
          // handle binding methods
          Bind_btnCombineCommand = new RelayCommand(BtnCombineCommand);
          Person = person;
          Animal = animal;
    }

    private void BtnCombineCommand()
    {
      //Bind_combineCombinedText = "Button has been pressed";

      Bind_combineCombinedText = Person.Bind_personNameText;
      Bind_combineCombinedText += " has a ";
      Bind_combineCombinedText += Animal.Bind_animalSpeciesText;
    }
  }
}

BTW: Falls du es mal mit ReactiveUI versuchen möchtest, da sieht so etwas ein wenig cleaner aus

public class MainWindowViewModel : ViewModelBase
{
    public CombinedViewModel Combined { get; }
    public MainWindowViewModel( CombinedViewModel combined )
    {
        Combined = combined;
    }
}

public class CombinedViewModel : ViewModelBase
{
    public AnimalViewModel Animal { get; }
    public PersonViewModel Person { get; }
    public ReactiveCommand<Unit, Unit> CombineCommand { get; }
    [Reactive] public string? Combined { get; private set; }

    public CombinedViewModel( AnimalViewModel animal, PersonViewModel person )
    {
        Animal = animal;
        Person = person;

        var canCombine = this.WhenAnyValue( e => e.Person.Name, e => e.Animal.Species, ( name, species ) => !string.IsNullOrEmpty( name ) && !string.IsNullOrEmpty( species ) );
        CombineCommand = ReactiveCommand.CreateFromTask( OnCombine, canCombine );
    }

    private async Task OnCombine( CancellationToken cancellationToken )
    {
        await Task.Delay( 250 );
        Combined = $"{Person.Name} - {Animal.Species}";
    }
}

public class PersonViewModel : ViewModelBase
{
    [Reactive] public string? Name { get; set; }
}

public class AnimalViewModel : ViewModelBase
{
    [Reactive] public string? Species { get; set; }
}

Und im Anhang das in einem Beispiel-Projekt mit DI-Container usw.

12.12.2023 - 12:44 Uhr

ViewModel können Daten auch über einen Messaging Dienst austauschen (z.B. ReactiveUI.MessageBus) allerdings würde ich in deinem Beispiel eher dahin tendieren dem MainWindowViewModel alle benötigten ViewModel per DI zu übergeben und als Eigenschaft zur Verfügung zu stellen.

11.12.2023 - 13:41 Uhr

Eigentlich legt doch der Frame - in dem diese Page angezeigt wird - fest, wie groß selbige dargestellt wird. So ist wenigstens mein Kenntnisstand.

09.12.2023 - 16:35 Uhr

Bei mir gilt der Grundsatz: Make it work (smart and readable), then fast (if needed). Und es ist schnell genug für diesen Anwendungsfall.

07.12.2023 - 15:16 Uhr

Meine Lösung sieht so aus:

    public int GetAnswerTwo()
    {
        return GetInput() // puzzle input as IEnumerable<string>
            .Select( e => new string( [GetFirstDigit( e ), GetLastDigit( e )] ) )
            .Select( int.Parse )
            .Sum();
    }

    private static readonly Dictionary<string, char> _digits = new Dictionary<string, char>
    {
        { "1", '1' },
        { "2", '2' },
        { "3", '3' },
        { "4", '4' },
        { "5", '5' },
        { "6", '6' },
        { "7", '7' },
        { "8", '8' },
        { "9", '9' },

        { "one", '1' },
        { "two", '2' },
        { "three", '3' },
        { "four", '4' },
        { "five", '5' },
        { "six", '6' },
        { "seven", '7' },
        { "eight", '8' },
        { "nine", '9' },
    };

    public static char GetFirstDigit( string s ) => 
        _digits
            .Select( e => (Value: e.Value, Index: s.IndexOf( e.Key )) )
            .Where( e => e.Index >= 0 )
            .OrderBy( e => e.Index )
            .Select( e => e.Value )
            .FirstOrDefault( '0' );

    public static char GetLastDigit( string s ) => 
        _digits
            .Select( e => (Value: e.Value, Index: s.LastIndexOf( e.Key )) )
            .Where( e => e.Index >= 0 )
            .OrderByDescending( e => e.Index )
            .Select( e => e.Value )
            .FirstOrDefault( '0' );
07.12.2023 - 11:32 Uhr

Nun stell dir vor, du hast folgenden Eingangswert: oneightdann liefert deine Variante als Zahl 11zurück. Richtig wäre allerdings 18.

04.12.2023 - 19:48 Uhr

Zur Information:

Zitat von CSharpNewbie2022

ich habe eine Liste von Informationen und ich möchte diese grafische darstellen. [...] Ich möchte das mit dem MVVM Design Pattern lösen.

Du benötigst ein Control, was dir eine Liste mit Strings so wie vorgegeben darstellt. Der von dir erwähnte MVVM Teil beschränkt sich dabei darauf, dass du im ViewModel solch eine Liste mit Strings zur Verfügung stellst und diese dann an das Control bindest. Fertig.

Du kannst also das MVVM Gedöns erst einmal vernachlässigen und dich auf das Design des Controls stürzen.

01.12.2023 - 08:24 Uhr

Wo ist denn das Problem bzw. wo ist das von dir erwartete Verhalten dokumentiert.

Wenn die Validierung fehl schlägt, dann bleibt bei entsprechender Einstellung der Focus auf der TextBox. Somit können andere Elemente, die den Focus benötigen nicht mehr erreicht werden. EinLabelgehört aber nicht zu diesen "Ich brauche den Focus" Elementen.