Bei "Always On Top" muss man entweder periodisch oder eventbasiert (so machen das die PowerToys) immer wieder überprüfen, ob das was "Always On Top" sein soll auch immer noch "On Top" ist und wenn nicht, dann eben wieder dort hinbringen.
Hast du dir schon mal UI.SyntaxBox angeschaut?
Ja, das ist eine TextBox
aber es geht eher um das Prinzip, wie die das da machen. Da wird auf jeden Fall das Binding überhaupt nicht gestört.
Das wäre eine Möglichkeit
<Window
x:Class="WpfApp1.MainWindow"
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:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow"
Width="800"
Height="450"
d:DataContext="{d:DesignData IsDesignTimeCreatable=False,
Type={x:Type local:MainWindowModel}}"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowModel />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider
x:Key="Difficulties"
MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:Difficulty" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<ListBox ItemsSource="{Binding Source={StaticResource Difficulties}}" SelectedItem="{Binding SelectedDifficulty}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button
MinWidth="80"
Margin="0"
Padding="5,2"
Command="{Binding DataContext.SelectDifficultyCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Content="{Binding}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
<Setter Property="IsHitTestVisible" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<Grid />
</DockPanel>
</Window>
public enum Difficulty
{
Easy,
Medium,
Hard
}
public class MainWindowModel : ReactiveObject
{
[Reactive]
public Difficulty SelectedDifficulty { get; set; }
public ReactiveCommand<Difficulty, Unit> SelectDifficultyCommand { get; }
public MainWindowModel()
{
SelectDifficultyCommand = ReactiveCommand.Create<Difficulty>( SelectDifficultyCommandExecute );
}
private void SelectDifficultyCommandExecute( Difficulty d )
{
SelectedDifficulty = d;
}
}
Möglicherweise ist aber auch der ToggleButton
etwas für dich
<ListBox ItemsSource="{Binding Source={StaticResource Difficulties}}"
SelectedItem="{Binding SelectedDifficulty}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding"
Value="0" />
<Setter Property="Margin"
Value="0" />
<Setter Property="Focusable"
Value="False" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton MinWidth="80"
Margin="0"
Padding="5,2"
Content="{Binding}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Ich habe da mal etwas vorbereitet und das funktioniert wunderbar
public partial class Form1 : Form
{
private readonly Random random = new Random();
public Form1()
{
InitializeComponent();
}
private Data GetRandomData()
{
return new Data
{
Name = "Daten",
Title = "Kurs",
Label = "Kurs",
DataPoints = Enumerable
.Range( 1, 31 )
.Select( x => new DataPoint { Date = new DateTime( 2025, 1, x ), Value = random.Next( 250 ) } )
.ToList()
.AsReadOnly(),
};
}
private void button1_Click( object sender, EventArgs e )
{
var data = GetRandomData();
chart1.SetData( data );
}
}
public static class ChartExtensions
{
public static Chart SetData( this Chart chart, Data data )
{
chart.Series.Clear();
chart.Series.AddData( data );
chart.ChartAreas.Clear();
chart.ChartAreas.AddData( data );
chart.Titles.Clear();
var title = chart.Titles.Add( data.Title );
title.Font = new System.Drawing.Font( "Arial", 16, FontStyle.Bold );
return chart;
}
public static Series AddData( this SeriesCollection collection, Data data )
{
Series series = new Series();
series.Name = data.Name;
series.ChartType = SeriesChartType.Column;
series.XValueType = ChartValueType.DateTime;
series.YValueType = ChartValueType.Auto;
data.DataPoints.ToList().ForEach( x => series.Points.AddXY( x.Date, x.Value ) );
collection.Add( series );
return series;
}
public static ChartArea AddData( this ChartAreaCollection collection, Data data )
{
var area = new ChartArea();
area.AxisX.Title = "Datum";
area.AxisY.Title = data.Label;
collection.Add( area );
return area;
}
}
public class Data
{
public string Title { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public IReadOnlyCollection<DataPoint> DataPoints { get; set; }
}
public class DataPoint
{
public DateTime Date { get; set; }
public decimal Value { get; set; }
}
Also hiermit
public class SomeController : ControllerBase
{
[HttpPost( Name = "SetData" )]
public IActionResult Post( [FromBody] Data model )
{
return Ok();
}
}
public class Data
{
[Required]
public Guid? RetailerId { get; set; }
[Required]
public string? Name { get; set; }
}
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext actionContext )
{
if ( actionContext.ModelState.IsValid == false )
{
actionContext.Result = new BadRequestObjectResult( actionContext.ModelState );
}
}
}
// Add services to the container.
builder.Services.AddControllers( options =>
{
options.Filters.Add<ValidateModelAttribute>();
} );
erhalte ich bei dem Aufruf
POST {{MyWebApi_HostAddress}}/some/
Content-Type: application/json
Accept: application/json
{
"retailerId": null,
"name": null
}
diesen response
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/problem+json; charset=utf-8
Date: Wed, 29 Jan 2025 09:25:58 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Name": [
"The Name field is required."
],
"RetailerId": [
"The RetailerId field is required."
]
},
"traceId": "00-5d5349a341853bf4ad3939fc19697e5b-0fbb3215c4907859-00"
}
Ist das nicht das was du suchst?
Dabei stelle ich fest, dass der Filter dafür gar nicht gebraucht wird.
Mach deine Seite mal ein klein wenig höher (so 760 Pixel) und dann schau mal genau hin 😃
Nur so zur Info, aber diese Zeile Code hier
this.DataContext = DataContext;
kannst du ersatzlos streichen. Du weist der Eigenschaft DataContext
den Wert der Eigenschaft DataContext
zu. Bildlich gesprochen nimmst du den 5-EUR-Schein aus deinem Portmonee und steckst diesen 5-EUR-Schein wieder dorthin zurück.
Zitat von Tom3419
Oder wo ist der Denkfehler?
Wenn du immer die GLEICHEN Werte bei der Form setzt, warum sollte die Form an einer anderen Stelle erscheinen?
Dieser Code
Screen monitor = Screen.AllScreens[screenIdx];
holt nur einen Wert aus einer Auflistung. Punkt. Mehr macht er nicht. Er stellet nichts irgenwie wo ein!
Der Code, der das was du möchtest macht, den habe ich schon hier gezeigt. Also ist es eher ein Lese- als ein Denkfehler
Zitat von Th69
Und wenn die übergebenen Parameter
xPos
/yPos
die relative Position angeben sollen, dann noch die Werte dazuaddieren:
Dann sollte man die aber auch konsequent umbenennen in xOffset
und yOffset
f3.Location = new Point(rect.X + xOffset, rect.Y + yOffset);
und man müsste diese Werte auch noch von der Höhe und Breite abziehen, sonst schiebt man das Fenster über den Bildschirmrand hinaus
Ich schaue mir mal den Code an:
private void Anzeige (int screenIdx, int xPos, int yPos, int xDim, int yDim)
{
// Den gewünschten Screen/Monitor auswählen - OK
Screen monitor = Screen.AllScreens[screenIdx];
// Den Arbeitsbereich des Monitors auslesen - OK
Rectangle rect = monitor.WorkingArea;
// Und nun? Was fängst du mit dem rect nun an?
f3.StartPosition = FormStartPosition.Manual;
f3.Location = new Point(xPos, yPos);
f3.Size = new Size(xDim, yDim);
f3.BackColor = Color.Red;
f3.Show();
}
Also du holst dir den Monitor, davon den Arbeitsbereich um dann damit nichts zu machen.
Die Arbeit kannst du dir doch auch sparen, und somit würde der folgende Code das Gleiche machen:
private void Anzeige (int screenIdx, int xPos, int yPos, int xDim, int yDim)
{
f3.StartPosition = FormStartPosition.Manual;
f3.Location = new Point(xPos, yPos);
f3.Size = new Size(xDim, yDim);
f3.BackColor = Color.Red;
f3.Show();
}
Wenn du das Fenster an den Arbeitsbereich des gewählten Screens anpassen willst, dann brauchst du aber die ganzen Parameter nicht, denn die Werte holst du dir aus der rect
Variable:
private void Anzeige (int screenIdx)
{
Screen monitor = Screen.AllScreens[screenIdx];
Rectangle rect = monitor.WorkingArea;
// Wir verwenden die Werte von rect
f3.StartPosition = FormStartPosition.Manual;
f3.Location = rect.Location;
f3.Size = rect.Size;
f3.BackColor = Color.Red;
f3.Show();
}
Schau mal in die Dokumentation von BitConverter da wird auch der Umgang mit Little- und. Big-Endian angesprochen
BTW Man kann hier ganz toll den Code direkt in den Beitrag einfügen, wenn man die Code-Tags verwendet. Sieht dann so aus:
namespace Foo;
class Bar
{
void Do( string s )
{
Console.WriteLine( s );
}
}
Du kopierst also alle Dateien von beiden Anwendungen in einen Ordner?
Genau das würde ich nicht machen - unter anderem aus dem Grund warum du gerade fragst.
Wenn du eine kleine Linux-Kiste (z.B. virtuell) aufstellst kannst du postfix
dafür verwenden.
Habe ich auch genau aus so einem Grund im Einsatz.
In Visual Studio ist die Erweiterung ResXManager sehr hilfreich.
dotnet publish -p:AssemblyName=appIntern […]
Zitat von Abt
Das erste was man braucht, ist eigentlich die WMI. Über GetProccess bekommt man immer nur die gleichen Prozesse der Architektur, also x86 oder x64. Über WMI bekommt man alles.
Das war die Einleitung zum Beitrag.
Um direkt in dem Code die Ergebnisse von Process.GetProcesses()
(wir erinnern uns: liefert zu wenig) und der WMI (wir erinnern uns: liefert alles) mit einem join
zu verknüpfen und damit das was die WMI mehr liefert gleich wieder in den Ausguss zu spülen.
Das macht einfach keinen Sinn weil man so nicht mehr bekommt.
Auf diese Problematik wollte ich den unbedarften Copy-Paste-Nutzer der Zukunft hinweisen, nicht das der dann da mit seinem kurzen Hemd im Regen herumsteht und die Welt nicht mehr begreift, denn das hilft schon mal gar nicht.
@jogibear9988
Es kommt eben nicht nur darauf an, was man ausführt, sondern auch wer
using System.Diagnostics;
using System.Management;
Console.WriteLine( "Processes: " + Process.GetProcesses().Count() );
Console.WriteLine( "WMI Processes: " + GetWmiProcesses().Count );
Console.WriteLine( "RunningProcesses:" + GetRunningProcessesFromWmi().Count );
static List<ManagementObject> GetWmiProcesses()
{
string wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process";
using ManagementObjectSearcher searcher = new( wmiQueryString );
using ManagementObjectCollection results = searcher.Get();
return results.Cast<ManagementObject>().ToList();
}
static List<(Process Process, string Path)> GetRunningProcessesFromWmi()
{
var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process";
using ManagementObjectSearcher searcher = new( wmiQueryString );
using ManagementObjectCollection results = searcher.Get();
var query =
from p in Process.GetProcesses() // Hier ist das große Mysterium, denn aus diesem weniger wird dann wieder mehr
join mo in results.Cast<ManagementObject>()
on p.Id equals (int)(uint)mo["ProcessId"]
select (p, (string)mo["ExecutablePath"]);
return query.ToList();
}
Mir ist es auch ein Rätsel, wenn Process.GetProcesses()
287 Items liefert und Abfrage der WMI 312 und dann ein JOIN aus beiden Abfragen (so wie bei GetRunningProcessesFromWmi()
) es auch 312 sein müssten, denn wofür sollte man das sonst so machen.
Aber ich stecke auch nicht so tief drin in der Materie, ich mache da bestimmt etwas falsch, oder habe hier die falschen Einstellungen oder einfach nur die Brille nicht geputzt. Das ist bei mir alles mehr als möglich.
Den Pfad als Key zu verwenden würde ich nicht empfehlen, denn bei mehreren Instanzen wird es zum Zufallsgenerator welche Instanz angezeigt wird. Die ProcessId wäre hier eindeutig.
Den Einwand mit GetProcess
kann ich irgendwie nicht nachvollziehen, denn diese Methode ist mir unbekannt.
Wenn damit Process.GetProcesses()
gemeint ist, ok, aber wenn das suboptimal ist, dann verstehe ich das hier nicht
var query = from p in Process.GetProcesses()
Kann aber natürlich sein, dass ich das alles nicht verstanden habe.
Alternativ geht auch ParallelEnumerable.AsParallel
das würde ohne lock
oder anderes Concurrent-Gedöns funktionieren.
Zitat von jogibear9988
Natürlich muss man in WPF nicht alles mit MVVM machen. Vor allem für jedes kleine Tool was man baut finde ich das overkill (ist meine Meinung).
Meine Meinung dazu ist, dass du fast den gleichen Code schreibst allerdings logisch und sauber getrennt in einzelnen Klassen (ViewModel, Model, Service).
Was du anscheinend meinst, ist der Aufwand das Grundgerüst für WPF/MVVM zu erstellen, was man benötigt um überhaupt anfangen zu können. Nun ich habe eine eigene Projektvorlage dafür. Ein Klick und alles ist vorbereitet für WPF/MVVM/DI-Container.
Den Code dann in die unterschiedlichen Klassen zu verteilen dauert kaum länger als deine Variante.
Es hat also eher etwas mit der eigenen Organisation zu tun.
Wird in der Forensuche bei Einschränkung nach Benutzernamen etwas eingetragen, dann erscheint nach dem Klick auf Suchen
Oops! Es ist leider ein Fehler aufgetreten 😕
Grundsätzlich würde ich erst einmal sagen, dass der Ansatz falsch gewählt ist.
So ist ein Getränke-Automaten ein Endlicher Automat und sollte zunächst auch so skizziert werden. Das ist erst einmal unabhängig von jeder Programmiersprache.
Anhand dieser Skizze geht man dann an die Umsetzung. Man kann dafür z.B. eine fertige Bibliothek (z.B. Stateless) nehmen oder auch selber umsetzen. Allerdings benötigt man hier auf jeden Fall die Grundlagen von OOP.
Ich habe dir mal ein kleines Projekt angehängt, wo du sehen kannst, was wir mit MVVM meinen und warum das am Ende "einfacher" ist.
Ja, es ist eine Umstellung wenn man nicht mehr alles auf einmal macht und danach ist es eben einfacher, weil man nicht mehr alles auf einmal macht.
Der eigentliche Teil (Logik) findet hier statt:
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<Dice> _dices;
public ReadOnlyObservableCollection<Dice> Dices { get; }
[Reactive] public int DiceCount { get; set; } = 6;
public ReactiveCommand<Unit, Unit> RollDicesCommand { get; }
public MainViewModel()
{
_dices = [];
Dices = new( _dices );
RollDicesCommand = ReactiveCommand.CreateFromTask( OnRollDiceCommandAsync );
}
private async Task OnRollDiceCommandAsync()
{
// Wir merken uns, wieviele Würfel wir werfen sollen wenn dieses Kommando gestartet wird
var diceCount = DiceCount;
// Alten Wurf leeren
_dices.Clear();
// Wir simulieren das Würfeln ... es dauert eben, bis die Würfel gerüttelt, geschüttelt, geworfen und zur Ruhe gekommen sind
await Task.Delay( 500 );
// Zufälliges Erzeugen der Würfelwerte
var randomDiceValues = Random.Shared.GetItems<DiceValue>( DiceValue.Values.ToArray(), diceCount );
// Aus den Würfelwerten erzeugen wir Würfel und tragen diese in die Liste ein, die den Wurf repräsentiert
foreach ( var item in randomDiceValues.Select( e => new Dice( e ) ) )
{
_dices.Add( item );
}
}
}
public record Dice( DiceValue Value );
public record DiceValue( int Value )
{
public int Value { get; } = Value < 1 || Value > 6 ? throw new ArgumentOutOfRangeException( nameof( Value ) ) : Value;
public static ImmutableHashSet<DiceValue> Values = [new( 1 ), new( 2 ), new( 3 ), new( 4 ), new( 5 ), new( 6 )];
}
Die (mehr oder wenige) ansprechende Darstellung findet an einer ganz anderen Stelle statt, weil diese bei dem Logik-Gedöns eh nur stört.
<DataTemplate x:Key="Template.Dice.Image" DataType="{x:Type vm:Dice}">
<Border Margin="5"
Padding="5"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="10">
<Image Height="60">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Value.Value}" Value="1">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.One}" />
</DataTrigger>
<DataTrigger Binding="{Binding Value.Value}" Value="2">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.Two}" />
</DataTrigger>
<DataTrigger Binding="{Binding Value.Value}" Value="3">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.Three}" />
</DataTrigger>
<DataTrigger Binding="{Binding Value.Value}" Value="4">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.Four}" />
</DataTrigger>
<DataTrigger Binding="{Binding Value.Value}" Value="5">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.Five}" />
</DataTrigger>
<DataTrigger Binding="{Binding Value.Value}" Value="6">
<Setter Property="Source" Value="{StaticResource Bitmap.Dice.Six}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Border>
</DataTemplate>
Fröhliches Herumspielen.
Wie würdest du das analog lösen? Also wenn du keinen Computer hast und du musst die Bestellung aufnehmen?
Wenn du dir das klar gemacht hast, dann suchst du nach einer Struktur, in der man das im Computer (Programmiersprache) abbilden kann. Und das hier ist ein klassisches Problem wofür man auch schon eine fertige Struktur erwarten kann (die es auch gibt).
Du kannst diese Würfel-Images im Constructor direkt nach InitializeComponent()
in ein Array schreiben um dieses dann für solche Zwecke zu verwenden.
Alternativ (und grundsätzlich empfehlenswert) ist die Verwendung von MVVM und Binding.
Warum sollte es nicht möglich sein ein Label im laufenden Betrieb dynamische Inhalte anzeigen zu lassen?
Bei WPF solltest du dringend auch MVVM verwenden, denn genau das ist so von MS so angedacht gewesen und man ist immer gut beraten eine Technologie so zu verwenden wie angedacht.
Man muss dabei die Denkweise etwas abstrakter halten und nicht immer sofort in Labels, Textboxen und Buttons denken. Man trennt in Was (will ich eigentlich darstellen) und Wie (will ich das am schönsten präsentieren).
Der klassische Weg (auch wegen Sicherheit) ist eine Web-API die für die Persitenz (Datenbank, Dateien, etc.) zuständig ist.
Für zwei Clients kann man das auch via VPN (z.B. FritzBox & Wireguard) über einen Heim-Server betreiben.
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
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.
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.
Zitat von CSharpNewbie2022
- 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 ..
- mit Schnittstellen (Ethernet, Ethercat, SPI, RS232, ...)
- 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.
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:
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.
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
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
down0
down0
up0
down0
up3
down3
up0
downALT
up0
upDamit wird RS übermittelt. Die anderen Zeichen analog. Damit solltest du jetzt deinen Barcode zusammenstellen können.
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.
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.
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?
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);
Die Antwort findest du auf stackoverflow.com.
Is so, liegt aber an Windows selber, dann dotnet leitet das nur an die Window Api weiter und das kommt dabei heraus.
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);
}
}
}
// 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();
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.
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;
}
}
Das ist ja eine lustige Logik in dem Programm. Auf jeden Fall ist die Userliste immer sehr schön aufgeräumt.
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?
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.
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?