Eine win32.dll
kenne ich von Windows nicht. Meinst du eventuell die Windows-API (formerly known as Win32-API)?
Dann sollte das eigentlich deine Frage beantworten:
Mithilfe der Windows-API können Sie Anwendungen entwickeln, die auf allen Windows-Versionen erfolgreich ausgeführt werden, und gleichzeitig die für jede Version einzigartigen Features und Funktionen nutzen. (Beachten Sie, dass dies früher als Win32-API bezeichnet wurde. Der Name Windows-API spiegelt seine Wurzeln in 16-Bit-Windows und seine Unterstützung unter 64-Bit-Windows genauer wider.)
Quelle: Windows-API-Index
Generell ist es eine gute Idee, sich beim Hersteller zu erkundigen.
Zitat von Kriz
-> Würde das hier gern schöner formatieren, aber es wird leider nicht richtig übernommen...
Du hast für deinen Code(-Block) die Code Formatierung verwendet und nicht den direkt daneben liegenden Button für Code-Block einfügen.
Mit Code-Block einfügen sieht das dann so aus:
switch (overlay.Type)
{
case OverlayType.Ellipse:
ShowEllipse = true;
ShowRectangle = false;
ShowPath = false;
break;
case OverlayType.Rectangle:
ShowEllipse = false;
ShowRectangle = true;
ShowPath = false;
break;
case OverlayType.Path:
ShowEllipse = false;
ShowRectangle = false;
ShowPath = true;
break;
default:
break;
}
Die Code Formatierung verwendest du, wenn du im Text etwas als Code formatieren möchtest, wie z.B. den Typen string
oder deinen overlay.Type
.
WÄre es nicht einfacher so?
public bool ShowEllipse => overlay.Type == OverlayType.Ellipse;
public bool ShowRectangle => overlay.Type == OverlayType.Rectangle;
public bool ShowPath => overlay.Type == OverlayType.Path;
und immer wenn sich in overlay
die Eigensschaft Type
ändert, dann folgenden Code noch ausführen
OnPropertyChanged(nameof(ShowEllipse));
OnPropertyChanged(nameof(ShowERectangle));
OnPropertyChanged(nameof(ShowPath));
Alternativ gäbe es auch noch diesen EnumToBoolConverter
aus dem CommunityToolkit
Zitat von RobertStahl
Perfekt, danke, aber das @GWu1 wird anscheinend benötigt für die Zuweisung im Update-Befehl
Ja, ähm ... genau ... ich habe doch auch nicht gesagt "entferne das @ Zeichen" sondern du musst bei Parameters.Add
den Parameter-Namen so verwenden wie du ihn im Statement geschrieben hast. Und zwar genau so.
Denn
Parameters can be prefixed with either
:
,@
, or$
.
aber für einen Präfix musst du dich zwingend entscheiden und der Parameter-Name ist immer mit Präfix anzugeben.
Wenn du es allerdings ganz bequem haben möchtest, dann kannst du auch Dapper verwenden. Dann würde das so aussehen:
class GrenzwerteDto
{
public int ProductReference { get; set; }
public decimal GWu1 { get; set; }
}
connection.Open();
var data = new GrenzwerteDto { ProductReference = 42, GWu1 = 12.45, };
connection.Execute( "UPDATE Grenzwerte SET GWu1 = @GWu1 WHERE REF_Produkt = @ProductReference", data );
oder mit noch weniger Boilerplate
connection.Open();
connection.Execute( "UPDATE Grenzwerte SET GWu1 = @GWu1 WHERE REF_Produkt = @ProductReference",
new { ProductReference = 42, GWu1 = 12.45, } );
Kann man sich überlegen
Da sind drei Dinge falsch.
command.CommandText = $"UPDATE Grenzwerte SET GWu1 = '@GWu1' WHERE REF_Produkt = 'Int aus einer Textbox'
Die Parameter kümmrn sich von ganz alleine darum, dass die Werte korrekt übergeben werden. Jede weitere Bevormundung durch Anführungszeichen o.ä. empfinden diese als übergriffig.
So ist es korrekt
command.CommandText = $"UPDATE Grenzwerte SET GWu1 = @GWu1 WHERE REF_Produkt = 'Int aus einer Textbox'
Parameters.Add
verwendenStatt
command.Parameters.AddWithValue("GWu1", DbType.Decimal).Value = NudGw_Gwu1.Value;
so
command.Parameters.Add("GWu1", DbType.Decimal).Value = NudGw_Gwu1.Value;
Der Name des Parameters lautet exakt so wie er in dem Command-Text angegeben wurde - wirklich exakt und kein "so irgendwie in der Art evetuell"
Wenn dort der Parameter also als @GWu1
angegeben wurde, dann ist genau das auch der Name.
Also statt
command.Parameters.Add("GWu1", DbType.Decimal).Value = NudGw_Gwu1.Value;
so
command.Parameters.Add("@GWu1", DbType.Decimal).Value = NudGw_Gwu1.Value;
Es ist etwas konfus was du da schreibst ... du könntest ruhig etwas konkreter werden, dann könnte man auch konkreter Hilfestellung geben.
Worauf du dich mit getters/setters beziehst erschließt sich mir nicht (im Code von Palladin sehe ich nur Getter).
Hast du dir das Spezification Pattern mal angesehen? Das ist eigentlich für so etwas der richtige Ansatz.
Hallo, du hast den klassischen Anfängerfehler (nicht falsch verstehen, den macht gefühlt jeder Programmierer auf seiner Reise - ich auf jeden Fall auch) gemacht.
Wenn du ein SQL-Statement per string zusammenbaust, dann ist das ein grober Fehler - Grüße von Little Bobby Tables.
Sicherer und einfacher funktioniert das mit Parametern denn die Parameter sorgen dafür, dass die Werte beim SQL-Server auch korrekt ankommen.
Ich sprach eigentlich von diesem VERSIONINFO
und nicht von System.Version
, denn dort passt es problemlos, weil dort für Build (und auch die anderen Teile) ein Int32
verwendet wird.
Wenn der Build bei 100.000 anfangen soll wird dann auch die Struktur in VERSIONINFO geändert?
Dort wird die Version ja als 64bit Wert angelegt (4x 16bit = 0..65535).
Meine Glaskugel ist in dem Bereich aber ganz wolkig, somit lassen wir uns überraschen.
Da wir nicht wissen, wie die Versionsnummern in der Zukunft weitergezählt werden, sollte man aber auch zwingend Major und Minor daraufhin prüfen, ob diese auch wirklich 10 bzw. 0 sind. Sonst ist die Verwirrung groß wenn (warum auch immer) Windows 12 die Version 10.1.12345.0 hat.
Kannst du mal mit DISKPART die Attribute des Laufwerks prüfen?
DISKPART
SELCT DISK 6
ATTRIBUTES DISK
Dann sollte er so etwas anzeigen
Aktueller schreibgeschützter Zustand: Nein
Schreibgeschützt : Nein
Startdatenträger : Nein
Auslagerungsdatei-Datenträger : Nein
Ruhezustandsdatei-Datenträger : Nein
Absturzabbild-Datenträger : Nein
Clusterdatenträger : Nein
Eventuell ist bei Windows 10 dort der Schreibschutz drin.
Probier mal diese kleine Änderung am Code:
public void AppendText(string text)
{
_text += text;
this.StateHasChanged();
}
Das riecht ein wenig nach Specification Pattern
Arbeitest du in deinem aktuellen Projekt mit DI?
Dazu würde ich auf jeden Fall raten egal ob MVVM oder nicht. Ich bevorzuge z.B. nuget:Wpf.Extensions.Hosting.
Wenn du dann irgendwann mit DI deine kompletten Abhängigkeiten auflöst hast, dann ist der nächste Schritt zu MVVM auch nicht mehr weit.
Main ruft MethodAAsync auf und Main ist blockiert. MethodeAAsync führt seine Befehle aus bis zum ersten await in der for-Schleife. Jetzt wird Main entblockiert und arbeitet die for-Schleife ab
Ja, kann man so sagen
MethodeAAsync wacht nach 100 ms auf, blockiert Main
Nein, definitiv nicht ... du siehst doch an deinem Log, dass beide auf verschiedenen Threads laufeen
und führt ein Console-Befehl aus
hier könnte es zu einer Blockierung kommen, wenn der Console-Befehl nur den Aufruf von einem Thread zulässt. Blockiert wird allerdings auch nur dann, wenn gleichzeitig von unterschiedlichen Thread dieser ausgeführt werden soll. Das ist wie ein Raum in dem nur eine Person Platz hat. Man muss wrten bis der Raum vom Vorgänger verlassen wurde und dann kann man rein. Wenn ich aber nicht rein muss, werde ich auch nicht blockiert.
und legt sich wieder schlafen.
...
Ist das so richtig zusammengefasst?
Nein, noch nicht ganz.
PS: Ich muss gerade an den Witz mit dem Frosch-Forscher denken. Der wo einem Frosch alle Gliedmaßen abschneidet und dann dem Frosch mit lauter Stimme befiehlt zu hüpfen. Als dieser nicht hüpft, schreibt er auf: "Frosch ohne Beine ist taub."
Also zu dieser Zeile
Task<int> taskA = MethodAAsync();
sagst du
Main startet
MethodAAsync
mitTask<int> taskA = MethodAAsync();
stellt es in den Hintergrund, kehrt direkt zurück und führt die for-Schleife.
Das stimmt so nicht, denn der Rücksprung erfolgt immer erst beim ersten await
wenn es auch wirklich etwas zum await
en gibt. Bei einem await Task.Delay(0)
erfolgt kein Rücksprung und bei jedem anderen erledigtem Task.
Das kann man auch sehen, wenn man etwas mehr Logging einbaut:
static async Task<int> MethodAAsync()
{
ConsoleWriteLine("A - ENTER");
for (int i = 0; i < 5; i++)
{
ConsoleWriteLine($" A{i}");
await Task.Delay(100);
}
int result = 123;
ConsoleWriteLine($"A returns result {result}");
return result;
}
static async Task Main(string[] args)
{
ConsoleWriteLine($"Main - ENTER");
Task<int> taskA = MethodAAsync();
ConsoleWriteLine("Main - before loop");
for (int i = 0; i < 5; i++)
{
ConsoleWriteLine($" B{i}");
Task.Delay(50).Wait();
}
ConsoleWriteLine("Wait for taskA termination");
await taskA;
ConsoleWriteLine($"The result of taskA is {taskA.Result}");
Console.ReadKey();
return;
}
Die Ausgabe sieht dann wie folgt aus
Main - ENTER Thread 1 MAIN
A - ENTER Thread 1 MethodA
A0 Thread 1 MethodA - jetzt kommt der await Task.Delay(100);
Main - before loop Thread 1 MAIN
B0 Thread 1 MAIN
B1 Thread 1 MAIN
A1 Thread 5 MethodA - ist jetzt in einem anderen Thread-Kontet unterwegs
B2 Thread 1
B3 Thread 1
A2 Thread 7
B4 Thread 1
Wait for taskA termination Thread 1
A3 Thread 7
A4 Thread 5
A returns result 123 Thread 7
The result of taskA is 123 Thread 7
Es ist doch für diese Frage völlig unerheblich, was du warum auch immer noch zusätzlich benötigst - für deine hier gestellte Frage spielt der EventAggregator keine Rolle.
Wenn du eine Frage zum Prism-Eventaggregator-ViewModel-Constructor hast, dann kannst du ja eine neue Frage stellen.
@Palladin007
Genau, die View darf so viel Code haben wie sie möchte, solange sich dieser Code nur um die Darstellung kümmert.
Das mit der Komplexität (ein NuGet-Package und ein Command) sehe ich nicht so, denn in meinen MVVM-Projekten verwende ich immer ReactiveUI
und warum sollte ich das für so eine Lösung nicht verwenden? Es sieht also nur scheinbar komplex aus aber im Gesamtbild macht es keinen Unterschied.
Ich hätte dieses mit einer AttachedProperty
gelöst, um die Bindung sehr elegant rein in XAML
zu definieren. Dadurch kann ich den zugrunde liegenden Code auch noch wiederverwenden - wenn man den in ein eigenens NuGet-Package verfrachtet sogar für mehrere Projekte.
So sähe das also bei mir aus:
<DockPanel>
<Label Content="Password:"
DockPanel.Dock="Top" />
<PasswordBox utils:PasswordBoxHelper.BindableSecurePassword="{Binding Password1}"
utils:PasswordBoxHelper.IsActive="True">
<PasswordBox.Style>
<Style TargetType="PasswordBox">
<Style.Triggers>
<DataTrigger Binding="{Binding PasswordIsValid}"
Value="True">
<Setter Property="Background"
Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
</DockPanel>
<DockPanel>
<Label Content="Passsword (retype):"
DockPanel.Dock="Top" />
<PasswordBox utils:PasswordBoxHelper.BindableSecurePassword="{Binding Password2}"
utils:PasswordBoxHelper.IsActive="True">
<PasswordBox.Style>
<Style TargetType="PasswordBox">
<Style.Triggers>
<DataTrigger Binding="{Binding PasswordsMatch}"
Value="True">
<Setter Property="Background"
Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
</DockPanel>
und das ViewModel
public class RegisterWindowViewModel : ReactiveObject
{
private readonly ObservableAsPropertyHelper<bool> _usernameIsValid;
private readonly ObservableAsPropertyHelper<bool> _passwordIsValid;
private readonly ObservableAsPropertyHelper<bool> _passwordsMatch;
public RegisterWindowViewModel()
{
_usernameIsValid = this.WhenAnyValue(
e => e.Username,
( u ) => !string.IsNullOrEmpty( u ) && u.Length >= 1 ) // darf auch gerne komplexer sein
.ToProperty( this, e => e.UsernameIsValid );
_passwordIsValid = this.WhenAnyValue(
e => e.Password1,
( p ) => p is not null && p.Length >= 1 ) // darf auch gerne komplexer sein
.ToProperty( this, e => e.PasswordIsValid );
_passwordsMatch = this.WhenAnyValue(
e => e.PasswordIsValid,
e => e.Password1,
e => e.Password2,
( p1valid, p1, p2 ) => p1valid && p1 is not null && p2 is not null && p1.Length == p2.Length && p1.IsEqualTo( p2 ) )
.ToProperty( this, e => e.PasswordsMatch );
var canRegister = this.WhenAnyValue(
e => e.UsernameIsValid,
e => e.PasswordsMatch,
( u, p ) => u && p );
Register = ReactiveCommand.CreateFromTask( OnRegisterAsync, canRegister );
}
private Task OnRegisterAsync( CancellationToken cancellationToken )
{
///
/// Hier die Registrierung durchführen
///
Username = string.Empty;
Password1 = null;
Password2 = null;
return Task.CompletedTask;
}
[Reactive] public string Username { get; set; } = string.Empty;
public bool UsernameIsValid => _usernameIsValid.Value;
[Reactive] public SecureString? Password1 { get; set; }
public bool PasswordIsValid => _passwordIsValid.Value;
[Reactive] public SecureString? Password2 { get; set; }
public bool PasswordsMatch => _passwordsMatch.Value;
public ReactiveCommand<Unit, Unit> Register { get; }
}
und noch der PasswordBoxHelper
public static class PasswordBoxHelper
{
public static SecureString GetBindableSecurePassword( PasswordBox obj )
{
return (SecureString)obj.GetValue( BindableSecurePasswordProperty );
}
public static void SetBindableSecurePassword( PasswordBox obj, SecureString value )
{
var old = obj.GetValue( BindableSecurePasswordProperty ) as SecureString;
obj.SetValue( BindableSecurePasswordProperty, value );
old?.Dispose();
}
public static readonly DependencyProperty BindableSecurePasswordProperty =
DependencyProperty.RegisterAttached( "BindableSecurePassword", typeof( SecureString ), typeof( PasswordBoxHelper ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSecurePasswordChanged ) );
private static void OnBindableSecurePasswordChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var pb = (PasswordBox)d;
if ( e.NewValue is null )
pb.Clear();
}
public static bool GetIsActive( PasswordBox obj )
{
return (bool)obj.GetValue( IsActiveProperty );
}
public static void SetIsActive( PasswordBox obj, bool value )
{
obj.SetValue( IsActiveProperty, value );
}
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached( "IsActive", typeof( bool ), typeof( PasswordBoxHelper ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsActiveChanged ) );
private static void OnIsActiveChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var pb = (PasswordBox)d;
if ( (bool)e.OldValue )
{
pb.PasswordChanged -= PasswordChanged;
}
if ( (bool)e.NewValue )
{
pb.PasswordChanged += PasswordChanged;
}
}
private static void PasswordChanged( object sender, RoutedEventArgs e )
{
var password = (PasswordBox)sender;
SetBindableSecurePassword( password, password.SecurePassword.Copy() );
}
}
Keine Ursache.
Nun ja, eigentlich ist es nicht wirklich anders ... der Teil, der bei dir nicht funktioniert (laden und anzeigen der Datei), den habe ich eigentich so gut wie unverändert übernommen.
Wenn also mein Projekt funktioniert und deins nicht, dann liegt es nicht an dem Code den du uns zeigst.
Lad doch mal dein nicht funktionierendes Projekt (so wie meins reduziert auf die RTF Anzeige) hier hoch dann können wir uns ansehen, was der wirkliche Störenfried da ist.
Eigentlich müsste das auch funktionieren.
Im Anhang mal eine Beispiel-Anwendung die genau das macht (wenigstens auf meinem System).
Probier es aus und wenn das auch bei dir funktioniert, dann vergleiche es mit deiner Anwendung.
Das geht mit Linq so
using System.Linq;
...
var Obstkorb = new [] { "Apfel", "Orange", "Moehre", "Banane", "Birne" };
var Obstliste = new [] { "Orange", "Moehre", "Banane", "Erdbeeren", "Ananas", "Traube", "Zitrone", "Pfirsich" };
var Rest = Obstkorb.Except( Obstliste ).ToArray();
Ich weiß ja nicht was du so vorhast, aber ich hätte das so gelöst (was auch funktioniert) und macht genau das, was du bisher so beschreiben hast. Man tippt einen beliebigen Wert und beim Drücken der Return-Taste wird ein Command aufgerufen und der eingegebene Wert steht im ViewModel zur Verfügung.
<xctk:DecimalUpDown Value="{Binding ValueTwo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<xctk:DecimalUpDown.InputBindings>
<KeyBinding Key="Return"
Command="{Binding DoSomething}"
CommandParameter="{Binding ValueTwo}" />
</xctk:DecimalUpDown.InputBindings>
</xctk:DecimalUpDown>
@TreaxP
Komplett rund ist die Lösung aber nicht, denn mit dem StringFormat
wird kein Wert dargestellt, das Feld bleibt leer. Taugt also nur für ein reines Eingabefeld.
Wenn das reicht, dann ist es eine ausreichende Lösung. Bei mehr würde ich ein passendes Control empfehlen.
@Th69
Meinn System ist auch auf deutsch und Thread.CurrentThread.CurrentCulture
sowie Thread.CurrentThread.CurrentCulture
stehen auf de-DE
und trotzdem muss ich in einer TextBox
als Dezimal-Trenner ein .
verwenden.
Um das zu ändern muss man beim Start der Anwendung folgendes aufrufen:
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof( FrameworkElement ),
new FrameworkPropertyMetadata( XmlLanguage.GetLanguage( CultureInfo.CurrentCulture.IetfLanguageTag ) ) );
Nun klappt es auch mit dem ,
.
Zitat von TreaxP
Wenn du mir noch eine letzte Frage erlaubst:
Ich bin in keinerlei Position hier etwas zu verbieten geschweige denn etwas zu erlauben 😃
Zu deiner eigentlichen Frage:
Wenn du dafür eine vernünftige Lösung haben willst, dann solltest du auf ein spezielles Control zurückgreifen, was explizit für die Eingabe eines double
oder decimal
Werts gedacht ist.
Da wäre z.B. DecimalUpDown
vom Extended.Wpf.Toolkit
So ein Control hat einen DataContext
.
Wird dieser nicht explizit gesetzt, dann wird dieser vom Parent durchgereicht.
Du hast diesen ja explizit auf Option1
gesetzt und somit wird dieses
<KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}" CommandParameter="{Binding RowNumber, Mode=OneWay}"/>
gegen diesen DataContext
versucht aufzulösen.
Aber genau das willst du ja nicht, sondern du willst das ja mit den Eigenschaften vom Control binden. Das habe ich dir schon gezeigt, hier aber noch einmal reduziert auf die wesentliche Teile:
<UserControl ...
x:Name="Control"
...>
...
<KeyBinding Key="Return"
Command="{Binding ElementName=Control, Path=CalculateOptionCommand}"
CommandParameter="{Binding ElementName=Control, Path=RowNumber}" />
...
</UserControl>
Allerdings sehe ich nicht, wo du das Binding für RowNumber
machst.
Hier auf jeden Fall nicht:
<uc:OptionsRow Grid.Row="0"
Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}"
DataContext="{Binding Option1}"
CalculateOptionCommand="{Binding RelativeSource={RelativeSource AncestorType=vm:MainViewModel}, Path=CalculateOptionCommand}"/>
Wenn du weiter Hilfe benötigst, dann solltest du ein reduziertes aber vollständiges Projekt hochladen, dann kann ich da einmal drüberschauen, entsprechende Änderungen vornehmen und dir zurückschicken.
Obwohl ich es persönlich wohl eher so lösen würde, dass das UserControl
eine DependencyProperty
fürOption
, CalculateCommand
und CalculateCommandParameter
bereitstellt.
Im Control würde das dann wie folgt gemappt:
<UserControl x:Class="UserControlBinding.WpfApp.OptionControl"
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:UserControlBinding.WpfApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Control"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<StackPanel>
<TextBlock Text="{Binding ElementName=Control, Path=Option.RowNumber, Mode=OneWay}" />
<TextBlock Text="{Binding ElementName=Control, Path=Option.BS, Mode=OneWay}" />
<TextBlock Text="{Binding ElementName=Control, Path=Option.PC, Mode=OneWay}" />
<TextBlock Text="{Binding ElementName=Control, Path=Option.Pr, Mode=OneWay}" />
<TextBox Text="{Binding ElementName=Control, Path=Option.KUl, Mode=TwoWay}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding ElementName=Control, Path=CalculateCommand}"
CommandParameter="{Binding ElementName=Control, Path=CalculateCommandParameter}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</Grid>
</UserControl>
und würde so verwendet
<uc:OptionsRow CalculateCommand="{Binding CalculateOptionCommand}"
CalculateCommandParameter="{Binding Option1.RowNumber}"
Option="{Binding Option1}"
Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />
Du hast CalcualteOptionCommand
vom DataContext
bezogen und für die Eigenschaften von der Option
Klasse hast du jeweils eine DependencyProperty
erstellt.
Ich dachte das wäre klar ... denn nun bekommst du die ganzen Option
Eigenschaften über den DataContext
und folglich den CalculateOptionCommand
... z.B. über eine DependencyProperty
. Es ist die gleiche Vorgehensweise aber nur mit vertauschten Rollen.
Denkbar wäre natürlich auch eine Erweiterung der Option
Klasse um genau so eine CalculateOptionCommand
Eigenschaft.
Hmmm, dann solltest du nochmal über die Konfiguration deiner IDE nachdenken, denn solche Dinge werden bei mir gleich vorgeschlagen und automatisch eingetragen.
Ansonsten sollte immer gehen: Cursor auf das problematische Element im Editor (also hier Cursors
) und dann STRG+.
da werden Sie geholfen
An Windows Desktop 7 liegt es schon mal nicht (siehe Cursors-Klasse).
Wohl eher an dem fehlenden using System.Windows.Input;
Das funktioniert bei mir ohne Probleme:
using System.Windows;
using System.Windows.Input; // Die muss da sein
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Cursor = Cursors.Arrow;
}
}
}
Also du hast dort eine Instanz, wo du die Events abonnierst:
DynamicMenuItems myObject = new DynamicMenuItems(); //Event deklarieren
myObject.MyEvent += myObject_MyEvent; //Event abonnieren
und dann erzeugst du eine andere Instanz um dort das anzulegen, was dann irgendwie dieses Event auslösen soll
var dmi = new DynamicMenuItems();
dmi.Menu = subtestToolStripMenuItem;
string[] items = { "SubMenu 1", "SubMenu 2", "SubMenu 3" };
dmi.AddMenuItems(items); //Menüeinträge aus einer List erzeugen
Das ist ungefähr so, wenn du dich benachrichtigen lässt, wenn bei Paul an der Wohnungstür jemand klingelt - du bei Sabine an der Wohnungstür auf die Klingel drückst und dich wunderst, warum du keine Benachrichtigung erhälst.
Es wäre sehr hilfreich wenn du deinen Code mit den entsprechenden Code-Tags posten würdest, das erhöht die Chance, dass sich überhaupt jemand die Mühe macht, deinen Code anzusehen.
Also statt so
namespace Hier
{
public class Da
{
public void Dort()
{
// ganz wichtig!
}
}
}
hätten wir es gerne so
namespace Hier
{
public class Da
{
public void Dort()
{
// ganz wichtig!
}
}
}
PS: Und statt myObject_MyEvent(Object objSender, EventArgs e) hätten wir gerne myObject_MyEvent(Object objSender, EventArgs e)
Auch so ein UserControl
hat ein DataContext
wo dann das normale Binding funktioniert.
<uc:OptionsRow Grid.Row="0" DataContext="{Binding Option1}"/>
und im UserControl
dann
<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding KUl, UpdateSourceTrigger=LostFocus}">
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}"
CommandParameter="{Binding RowNumber, Mode=OneWay}"/>
</TextBox.InputBindings>
</TextBox>
Der Zugriff auf CalculateOptionCommand
geht so dann natürlich nicht mehr (wegen dem geänderten DataContext
).
Wenn du deiner DynamicMenuItems
Klasse die Instanz zu Form1
übergibst, dann kannst du dir diese dort merke und dann auch auf die Methode zugreifen.
namespace Eugen.ESystem
{
public class DynamicMenuItems
{
private readonly Form1 _form;
public DynamicMenuItems( Form1 form )
{
_form = form;
//Mache hier irgendwann irgendetwas
}
/// <summary>
/// List of menu entries to be created
/// </summary>
public string[] MenuItems { get; set; }
/// <summary>
/// Menu into which the submenus are inserted
/// </summary>
public ToolStripMenuItem Menu { get; set; }
/// <summary>
/// Insert a menu item
/// </summary>
/// <param name="menuItem">Name of the Menu item</param>
public void AddMenuItem(string menuItem)
{
ToolStripMenuItem menu = (ToolStripMenuItem)(Menu);
Menu.DropDownItems.Add(menuItem, null, new EventHandler(ToolStripMenuItem_Click));
}
/// <summary>
/// Insert menu entry from a list
/// </summary>
/// <param name="menuList">List of menu items</param>
public void AddMenuItems(string[] menuList)
{
//Für jedes Element in der Liste einen Menüpunkt anlegen
foreach (var element in menuList)
{
AddMenuItem(element);
}
}
/// <summary>
/// Check a menu item
/// </summary>
/// <param name="selectedItem">Item name</param>
public void CheckMenuItem(string selectedItem)
{
//Hier soll der Menüeintrag, der dem Übergebenen Wert entspricht, auf 'Checked = true' gesetzt werden
foreach (ToolStripMenuItem item in Menu.DropDownItems)
{
if (item.Text == selectedItem)
{
item.Checked = true;
}
else
{
item.Checked = false;
}
}
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
ToolStripMenuItem MenuItem = (ToolStripMenuItem)sender;
CheckMenuItem(MenuItem.Text);
MessageBox.Show("Menu " + sender + " angeklickt");
DoSomething();
_form.DoSomethingInForm1(); //Hier liegt das Problem !!!
}
public void DoSomething()
{
OnMyEvent(EventArgs.Empty);
MessageBox.Show("Jetzt sind wir hier");
// ...
}
public event EventHandler MyEvent;
protected virtual void OnMyEvent(EventArgs e)
{
EventHandler myEvent = MyEvent;
if (myEvent != null)
{
myEvent(this, e);
}
}
}
}
hier der Code von Form1
:
namespace Test_DynamicMenuItems
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//...
}
public static string Value { set; get; }
private void buttonAddmenuItems_Click(object sender, EventArgs e)
{
var dmi = new DynamicMenuItems( this );
dmi.Menu = subtestToolStripMenuItem;
string[] items = { "SubMenu 1", "SubMenu 2", "SubMenu 3" };
dmi.AddMenuItems(items); //Menüeinträge aus einer List erzeugen
DynamicMenuItems myObject = new DynamicMenuItems( this );
myObject.MyEvent += myObject_MyEvent;
//myObject.DoSomething();
}
public static void myObject_MyEvent(Object objSender, EventArgs e)
{
MessageBox.Show("myObject_MyEvent (" + objSender + ", " + e + ")");
}
public static void DoSomethingInForm1()
{
// jetzt kommen wir hier auch hin
MessageBox.Show("Juhu, es hat funktioniert");
//Und da passieren noch ganz viele andere Dinge
}
}
}
So richtig gesund ist das irgendwie nicht - allerdings sehe ich noch nicht so richtig, was für ein Problem du damit eigentlich lösen möchtest.
PS: Deinen Code solltest du mit Code-Tags versehen, dann ist der auch so leserlich wie meiner hier.
Erklär doch bitte mal warum du diese wiederholende Zuweisung benötigst.
Auch der Setter der Eigenschaft ist eher suboptimal. So kann es passieren, das trotz Änderung des Eigenschaftswertes kein PropertyChanged
ausgelöst wird.
Als weiteren Kritikpunkt hätte ich das mit der Verwendung des UI-Typen Visibility
im ViewModel. Der hat da nichts zu suchen. Wenn es nur zwei Zustände gibt, dann nimm einen bool
Wert dafür und in der View setzt du diesen bool
Wert in das um, was dort am besten ist. Im ViewModel sollte man keinen Gedanken daran verschwenden, wie man etwas darstellt, nur was man darstellen möchte.
Zum Setter gäbe es noch mehr zu sagen, aber das führt zu weit und erledigt sich evtl. wenn du uns erklärst, was du da erreichen möchtest.
Hier ein weiterer von den >100 möglichen Wegen:
Mit der DataGridTemplateColumn kann man das wie folgt umsetzen:
<Window
x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="clr-namespace:System.Collections;assembly=System.Runtime"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<col:ArrayList x:Key="projects">
<local:Projekt ID="1" ProQuali="0" />
<local:Projekt ID="2" ProQuali="1" />
<local:Projekt ID="3" ProQuali="2" />
</col:ArrayList>
</Window.Resources>
<Grid>
<DataGrid
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{StaticResource projects}">
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding ID}"
Header="Id"
IsReadOnly="True" />
<DataGridTextColumn
Binding="{Binding ProQuali}"
Header="Qualität"
IsReadOnly="False" />
<!--#region Template Column-->
<DataGridTemplateColumn Header="Qualität">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Ellipse Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}" Margin="2">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProQuali}" Value="1">
<Setter Property="Fill" Value="Yellow" />
</DataTrigger>
<DataTrigger Binding="{Binding ProQuali}" Value="2">
<Setter Property="Fill" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System.Windows;
namespace WpfApp1;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class Projekt
{
public int ID { get; set; }
public int ProQuali { get; set; }
}
Wäre es da nicht besser, dem Kinde gleich die richtigen Namen zu verpassen?
Du hast es doch mit dem Schema quasi schon fertig:
class Section
{
public string Header { get; set; }
public Dictionary<string,string> Values { get; set; }
}
class Packet
{
public string Description { get; set; }
public List<Section> Sections { get; set; }
}
Eigentlich ist es ganz einfach:
Jeder Typ muss explizit im DI-Container registriert werden, damit dieser auch aufgelöst werden kann.
Wenn du also den Typ IWorkerTicketProvider<MyWorker> irgendwo injecten möchtest, dann musst du IWorkerTicketProvider<MyWorker> auch explizit registrieren, so wie du das jetzt eben gemacht hast.
Ich möchte hier auch mal mit mischen.
Schauen wir uns doch mal 2 mögliche Angriffsvektoren an und wie die PasswordBox bzw. der darin gekapselte SecureString da schützen.*Keylogger
NEIN, kein Schutz, denn die Information wird abgegriffen bevor diese den SecureString überhaupt erreicht
*Speicher auslesen
JEIN, nur bedingt, denn irgendwann muss man die Hosen runterlassen und das Password muss in Klartext (als string oder char[]) an die entsprechende Authentifizierungs-Logik übergeben werden und dann ist das Kennwort wieder abgreifbar (je nach Umsetzung für eine kurze oder sehr lange Zeitspanne)
Wenn man sich also auf den SecureString verlässt, dann hat man lediglich die Script-Kiddies ausgesperrt, die mal eben so im Speicher nach Passwörtern Ausschau halten und dann aufgeben, wenn sie da nichts finden. Für alle anderen - die auf wirklichen Schaden aus sind - stellt das keine ernst zu nehmende Hürde dar und ist damit gleichbedeutend wie gar kein Schutz.
Die Lösung dafür liegt eher im Anmelde-Konzept, wie z.B.*Anmeldung mit 2FA *Passwortlose Anmeldung (wie z.B. die Anmeldung bei Office 365 mit der MS Authenticator APP)
Dort spielt es keine Rolle, ob das Kennwort erbeutet wird, bzw. es gibt einfach keins zu erbeuten.