Ich habe das oben einmal alles durchprobiert und das Ergebnis ist immer noch die NullReferenceException. Aber ich konnte den Fehler zumindest etwas eingrenzen.
Dieser Teil des Codes hat immer funktioniert:
int buttonID = 1;
ushort normalGraphic = 0x07ef;
ushort pressedGraphic = 0x07f0;
ushort overGraphic = 0x07f1;
string caption = "";
byte font = 0;
bool isUnicode = true;
ushort normalHue = ushort.MaxValue;
ushort hoverHue = ushort.MaxValue;
Type type = Engine.ClassicAssembly.GetType("ClassicUO.Game.UI.Controls.Button");
object button = Activator.CreateInstance(type, buttonID, normalGraphic, pressedGraphic, overGraphic,
caption, font, isUnicode, normalHue, hoverHue);
FieldInfo boundsInfo = type.BaseType.GetField("_bounds", BindingFlags.NonPublic | BindingFlags.Instance);
object bounds = boundsInfo.GetValue(button);
Heißt, die instance von _bounds war immer als Rectangle gesetzt.
Das Nachfolgende war dann die Trail&Error-Phase, unterbrochen durch viel Suchen und Lesen. Ich habe die gescheiterten Versuche dringelassen und auskommentiert, damit sie nachvollzogen werden können. Am Ende der Zeilen stehen die Debug-Ergebnisse.
// 1
//FieldInfo xkInfo = bounds.GetType().GetField("<X>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // null
// 2
//FieldInfo xInfo = bounds.GetType().GetField("X", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // gesetzt
//bool hasBackingField = xInfo.IsDefined(typeof(CompilerGeneratedAttribute), false); // false
//FieldInfo xkInfo = xInfo.DeclaringType.GetField("<X>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // null
// 3
// FieldInfo xkInfo = bounds.GetType().GetField("<Left>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // null
// 4
PropertyInfo leftInfo = bounds.GetType().GetProperty("Left", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // null
bool hasBackingField = leftInfo.IsDefined(typeof(CompilerGeneratedAttribute), false); // false
FieldInfo xkInfo = leftInfo.DeclaringType.GetField("<Left>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // null
// hier krachte es immer bei SetValue, da xkInfo bei allen Versuchen null war.
xkInfo.SetValue(bounds, "10");
xkInfo
war null.xInfo
gesetzt, aberxkInfo
war null. Habe aber noch den hasBackingField bool eingefügt, der anzeigen soll, ob der Compiler ein BackingField erzeugt hat. Ich weiß nicht, inwieweit das funktioniert, war immer false.xkInfo
war auch hier immer null.Edit:
Stand ich gerade auf dem Schlauch! Beim Durchlesen meines Post ist es mir dann aufgefallen. X ist ein Field, also nicht das BackingField versuchen auszulesen, sondern gleich X setzen! Habe ja noch dazugeschrieben, daß es gesetzt war. Meine Güte, manchmal kann man sich echt selbst auf dem Fuß stehen.
FieldInfo xInfo = bounds.GetType().GetField("X", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); // gesetzt
xInfo.SetValue(bounds, 10);
Das ging jetzt.
public int X => _bounds.X;
Die beiden sind im Client Code jeweils mit ref versehen.
public ref int X => ref _bounds.X;
Ich werde den zweiten Teil deines Code einmal mit "Rectangle _bounds" ausprobieren.
Probier mal
BindingFlags.FlattenHierarchy
Hat leider nicht funktioniert. Es wird weiterhin die NullReferenceException geworfen.
Aber Microsoft hat da in letzter Zeit so viel optimiert, dass ich mir nicht sicher bin, ob das überhaupt relevant ist.
In "letzter Zeit" hört sich schon mal schlecht an, da ich mit .Net Framework 4.8 unterwegs bin. Der Client erfordert ein Assemby Objekt von den Plugins und wie es aussieht, funktioniert nur ein Framework 4.8 Assembly. Ich es habe anfangs mit .Net 7 versucht und das Plugin wurde nicht angenommen. Leider gibt es überhaupt keine Dokumentation zu Plugins und man muss sich alles selbst aus dem Client Code und aus den paar existierenden Plugins erlesen. Habe mich schon gefragt, ob man dem Client nicht irgendwie ein .Net 7 Assembly als Framework 4.8 "vorgaukeln" könnte. Denn dann könnte ich sogar unter Linux arbeiten. Habs aber nicht geschafft und aufgegeben.
Du darfst ja auch nicht vergessen, dass Reflection generell eher langsam ist, auch wenn's besser geworden ist.
Wenn das so ist, dann sollte ich meine Herangehensweise noch einmal überdenken. Denn ich werde Performance brauchen. Ein custom Inventory anzuzeigen wäre leicht verzügert ja noch verträglich. XX Items auf einen Schlag dort einfügen, wahrscheinlich eher nicht. GumpPics und Buttons (die Items sind klickbar und das wird mit "getarnten" Buttons realisiert) rüberholen, erstellen und dann wieder zum Client zurückschicken. Ich weiß nicht, ob diese Prozedur, bestehend aus vielen Aufrufen von Methoden und Setzen von Property Values via Reflection, nicht den Client zum Laggen bringt. Und dann noch die Cooldown Bars, die ich machen wollte, die alle 1/10 Sekunde akutalisiert werden sollten.
Um einmal kurz laut zu denken:
Eigentlich wollte ich den Client Code nicht anfassen, aber ich werde den Verdacht nicht los, als käme ich nicht herum. Ich könnte eigene Klassen dort platzieren, die das alles erzeugen und steuern und sie dann per Reflection aufrufen. Das würde wiederum die Delegates interessant machen. Aber ich verstehe nicht, wie man ein Delegate mit einer "reflektierten" Methode / Property verknüpfen kann.
Ich habe die Reflector Klasse um GetFieldValue erweitert und versucht _bounds auszulesen. Es wurde ständig die NullReferenceException geworfen, mit der Meldung: Object reference not set to an instance of an object. Im Debug-Mode war aber alles gesetzt, wie es sollte.
Meine Güte, was habe ich mich gerade totgesucht. Aber, zu erst, das hier ist _bounds:
private Rectangle _bounds;
Nach viel aufsteigendem Rauch aus meinem Kopf, fiel mir ein, _bounds befindet sich in der Klasse Control, die Button erbt und ist somit für Button nicht sichtbar. Ich sätze, da kommt man auch mit Reflection nicht ran, oder?
Ich persönlich schreibe auch immer noch instance dazu.
Das wäre meine Nebenfrage gewesen. Da ich im Reflector jede Methode jeweils mit BindingFlags-Parameter überlade, macht es Sinn, um sich die Überladungen zu sparen, wenn ich den hier mache...
private readonly BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static;
... und das überall per default übergebe? Performance-Einbrüche würde das nicht verursachen, oder?
Ja, das hätte _instance sein müssen. Ist korrigiert.
BindingFlags brauche ich in diesem Fall tatsächlich nicht, da die Properties und Methoden public sind. Die Klassen sind zwar internal, aber das spielt keine Rolle. Der Zugriff drauf ist der gleiche, wenn ich das richtig gelesen habe. Ich hab es trotzdem mit Public und NonPublic BindingFlags versucht und es wurde die selbe Exception geworfen.
Habs debugt, fand aber keine Info zu Gettern und Settern, nur die Auflistung der Properties mit Bezeichner und Type. Aber ich habe mir den Client Code noch einmal angeschaut und ich weiß nicht, was ich gestern da zur späten Stunde gesehen habe, aber heute fand ich das in der geerbten Klasse des Buttons:
public ref int X => ref _bounds.X;
Kein Setter! _bounds ist ein Rectangle vom Microsoft.Xna Framework, auf dem der Client basiert. Muss ich jetzt mein Vorhaben begraben oder kann ich die Referenz mit Reflection irgendwie rüberholen?
Ich habe keinen direkten Zugriff auf die Klassen. Ich arbeite an einem Plugin für einen Ultima Online Client. ClassicUO, heißt er, wurde in C# geschrieben und ist Open Source. Das Plugin wird vom Client geladen, also lade ich die Assembly vom Client und bekomme Zugriff auf seine Klassen. Will mir die GUI etwas anpassen.
Ich kann Gumps erstellen, sie befüllen und zurück an den UIManager des Clients übergeben. Sie werden auch im Spiel angezeigt. Auch die Button Klasse, um die es oben geht, kann ich mit dem Reflector instanzieren und sie wird angezeigt. Aber halt bei der Koordinate 0, 0. Sobald ich X setze, kracht es.
Hier ein Beispiel, wie der Client die Klasse instanziert:
Add
(
new Button((int)Buttons.Help, 0x07ef, 0x07f0, 0x07f1)
{
X = 185,
Y = 44 + 27 * 0,
ButtonAction = ButtonAction.Activate
}
);
Und so greife ich drauf zu:
public class Button : Control
{
public int X
{
get => Reflector.GetPropertyValue<int>("X");
set => Reflector.SetPropertyValue("X", value);
}
public int Y
{
get => Reflector.GetPropertyValue<int>("Y");
set => Reflector.SetPropertyValue("Y", value);
}
public int ButtonAction
{
get => Reflector.GetPropertyValue<int>("ButtonAction");
set => Reflector.SetPropertyValue("ButtonAction", value);
}
// Die Args habe ich vom Client copy-pastet.
public Button
(
int buttonID,
ushort normalGraphic,
ushort pressedGraphic,
ushort overGraphic = 0,
string caption = "",
byte font = 0,
bool isUnicode = true,
ushort normalHue = ushort.MaxValue,
ushort hoverHue = ushort.MaxValue
)
{
Reflector.SetClass("ClassicUO.Game.UI.Controls.Button");
Reflector.CreateInstance(buttonID, normalGraphic, pressedGraphic, overGraphic, caption, font,
isUnicode, normalHue, hoverHue);
}
}
// GumpPic, Gump und UIManager sind, wie die Button-Klasse "reflectiert".
var pic = new GumpPic(0, 0, 0x07d0, 0);
var button = new Button(1, 0x07ef, 0x07f0, 0x07f1);
button.X = 185; // Hier kracht's. Ohne diese Zeile wird alles angezeigt.
var gump = new Gump();
gump.Add(pic);
gump.Add(button);
UIManager.Add(gump);
Moin,
ich schreibe gerade einen kleinen Reflection Wrapper und komme beim Setzten von Property Values nicht weiter. Es kommt immer diese Exception:
System.ArgumentException: 'Property set method not found.'
Ich habe mich vergewissert, daß das Property, an dem ich den Wrapper ausprobiere, Getter und Setter hat. Mein einziger Anhaltspunkt ist, daß ich beim "Source Object" was falsch mache. In allen Beispielen, die ich finde, wird als Source Object eine zuvor erstellte Instance einer Klasse genommen, die dann der Methode SetValue übergeben wird, wie z.B. hier https://learn.microsoft.com/de-de/dotnet/api/system.reflection.propertyinfo.setvalue?view=net-7.0
...
Example exam = new Example();
...
PropertyInfo piInstance = examType.GetProperty("InstanceProperty");
piInstance.SetValue(exam, 37);
...
Ich habe aber keine solche Klasse, da sie selbst per Reflection instanziert wird. Ist das richtig, daß ich in diesem Fall diese Instanz als Parameter übergebe? (Alles andere funktioniert.)
Das ist der Wrapper:
public class Reflector
{
private Assembly _assembly;
private Type _type;
private object _instance;
public object Instance { get => _instance; }
public Reflector(Assembly assembly) =>
_assembly = assembly;
public Type SetClass(string className) =>
_type = _assembly.GetType(className);
public object CreateInstance(params object[] args) =>
_instance = Activator.CreateInstance(_type, args);
public T GetPropertyValue<T>(string propertyName, BindingFlags bindingFlags) =>
(T)_type.GetProperty(propertyName, bindingFlags).GetValue(_type, null);
public T GetPropertyValue<T>(string propertyName) =>
(T)_type.GetProperty(propertyName).GetValue(_type, null);
public void SetPropertyValue(string propertyName, object value, BindingFlags bindingFlags) =>
_type.GetProperty(propertyName, bindingFlags).SetValue(_instance, value);
public void SetPropertyValue(string propertyName, object value) =>
_type.GetProperty(propertyName).SetValue(_instance, value);
public T InvokeMethod<T>(string methodName, BindingFlags bindingFlags, params object[] args) =>
(T)_type.GetMethod(methodName, bindingFlags).Invoke(_instance, args);
public T InvokeMethod<T>(string methodName, params object[] args) =>
(T)_type.GetMethod(methodName).Invoke(_instance, args);
public void InvokeMethod(string methodName, BindingFlags bindingFlags, params object[] args) =>
_type.GetMethod(methodName, bindingFlags).Invoke(_instance, args);
public void InvokeMethod(string methodName, params object[] args) =>
_type.GetMethod(methodName).Invoke(_instance, args);
}
Ich hätte noch zwei Fragen.
Wie man auf dem Screenshot sehen kann, haben die beiden ListBoxen ein Hover-Effekt, den ich da nicht haben möchte. Ich habe schon viele Code-Schnippsel ausprobiert und nicht gehts. Es kommen keine Fehler, aber auch keine Wirkung.
Eigentlich brauche ich auch keine Liste in dem Sinne, sondern eher eine Tabelle. Kann man nicht einfach einen Grid dynamisch befüllen?
Dann wäre noch die TextBox, die sich nicht warpen lassen will. Die "Command"-Zeile ist ewig lang und egal was ich nicht probiere, da kommt kein Zeilenumbruch. Komischerweise geht das aber mit der TextBox, die Raw Data anzeigt, obwohl es die gleiche ist. Habe sie copy-pastet. Raw Data ist auch nur eine lange Zeile. Ich möchte keine fixen Breiten angeben, da sich das Layout dem Fenster anpassen soll.
<Window x:Class="PacketViewer.UI.View.PacketDetailsWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PacketViewer.UI.View"
mc:Ignorable="d"
Title="Packet Details" Height="400" Width="570">
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Grid
Grid.Column="0" Grid.Row="0"
Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
Grid.Row="0"
Header="Packet"
Padding="10"
Background="AliceBlue">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="ID: " FontWeight="Bold"/>
<TextBlock Text="Name: " />
<TextBlock Text="Length: " />
<TextBlock Text="Sent by: " />
<TextBlock Text="Description: "/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="7, 0, 0, 0">
<TextBlock Text="{Binding ID}" FontWeight="Bold"/>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Length}" />
<TextBlock Text="{Binding SentBy}" />
<TextBlock Text="{Binding Description}"/>
</StackPanel>
</Grid>
</GroupBox>
<GroupBox
Grid.Row="1"
Header="Details"
Padding="10"
Background="AliceBlue">
<ListBox
ItemsSource="{Binding Details}"
Width="Auto"
Background="AntiqueWhite"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Header}" FontWeight="Bold"/>
<ListBox
Grid.Row="1"
ItemsSource="{Binding Data}"
Margin="10,0,0,0"
Background="Transparent"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding Path=DataName}"/>
<TextBox
Width="Auto"
Margin="7,0,0,0"
Grid.Column="1"
Text="{Binding Path=DataValue}"
TextWrapping="Wrap"
Background="Transparent"
BorderThickness="0"
IsReadOnly="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox
Grid.Row="2"
Header="Raw Data"
Padding="10"
Background="AliceBlue">
<TextBox
Width="Auto"
Grid.Column="1"
Text="{Binding Path=RawData}"
TextWrapping="Wrap"
Background="Transparent"
BorderThickness="0"
IsReadOnly="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</GroupBox>
</Grid>
</ScrollViewer>
</Window>
Vielen Dank für eure Antworten. Ich stand wohl gewaltig auf dem Schlauch, daß ich nicht drauf kam eigene Klassen dafür zu nehmen. Irgendwie bin ich gedanklich am Key-Value hängen geblieben.
Moin,
ich bin gerade dabei ein PacketViewer zu erstellen, der die Packets von diesem Client-Server-Protocol auslesen und anzeigen soll. Das Auslesen der Packets und deren IDs in einer Liste darstellen, klappt schon soweit. Nun möchte ich, daß wenn man auf einen der ListItems klickt, die Details zu dem jeweiligen Packet in einem neuen Fesnter angezeigt werden. Klick-Funktion und neues Fenster funktionieren auch schon.
Beim Anzeigen der Daten komme ich aber nicht weiter. Ich habe es mir nach dem folgenden Schema vorgestellt:
`
Beschreibung des Packets als Text.
Überschrift:
key1: value1
key2: value2
Überschrift:
key3: value3
key4: value4
key5: value5
key6: value6
Überschrift:
...`
Einige Packets haben kaum Daten andere wiederum sind sehr datenreich. Es sind insgesamt 198, größtenteils sehr unterschiedliche Packets und ich möchte nicht für jedes Packet eine View erstellen müssen. Eine Idee wäre es mit einer Dictionary zu versuchen und dann alles irgendwie zu parsen:
dict.add("*headline*", "Überschrift");
dict.add("key1", "value1");
dict.add("key2", "value2");
dict.add("*headline*", "Überschrift");
...
Ich habe nur keinen blassen Schimmer, wie man so etwas mit MVVM generiert. Bei jeder Headline muss es irgendwie ein Column-Span geben.
Ist das überhaupt eine gute Idee oder gibt es bessere Lösungsansätze? Und wenn ja, wie realisiert man das?
Was mir noch aufgefallen ist, aber ich bin absoluter Anfänger, warum hast du eine Klasse PluginList die eine IList<Plugin> hat und im ViewModel eine ObservableCollection<Plugin> PluginList.
Brauch man da die Klasse überhaupt?
Ja, ich brauche die Klasse. In der GUI hinzugefügte Plugins speichere ich in einer Json-Datei. IList<Plugin> wird beim Start vom Json's Deserializer mit Plugin-Objekten befüllt. Danach werden die Plugins ausgelesen, mit zusätzlichen Daten versehen und anschließend in die ObservableCollection<Plugin> eingetragen.
Es geht also darum, daß das Objekt (in diesem Fall also Plugin) auch diese Schnittstelle implementiert haben muß - die ObservableCollection<T> benachrichtigt nur beim Änderungen an der Auflistung selbst (Add, Remove, Clear), nicht bei Änderungen der Eigenschaften der enthaltenen Objekte.
Das war dann der Fehler. Ich bin davon ausgegangen, daß ObservableCollection auch die Objekte managet.
Für das Plugin selbst nicht. Das Plugin ist in der ObservableCollection<Plugin> Klasse. Muss ich das noch für die Plugin-Klasse machen?
(Nur damit keine Verwirrung entsteht, ich habe inzwischen State in Status umbenannt.)
Plugin:
public class PluginList
{
public IList<Plugin> Plugins;
}
public class Plugin
{
/// <summary>
/// Represents the project path where the plugin is built.
/// </summary>
public string ProjectPath { get; set; }
/// <summary>
/// Determines if the plugin is loaded on start up.
/// </summary>
public bool AutoLoad { get; set; }
/// <summary>
/// Determines if the plugin is automaticly reloaded when changed in the project directory.
/// </summary>
public bool AutoReload { get; set; }
/// <summary>
/// Represents the current state of the plugin.
/// </summary>
[JsonIgnore] public PluginStatus Status { get; set; }
/// <summary>
/// Represents the absolute path (+file name) of the plugin in the application's plugin directory.
/// </summary>
[JsonIgnore] public string Path { get; set; }
/// <summary>
/// Instance of the AppDomain that was created for the plugin.
/// </summary>
[JsonIgnore] public AppDomain Domain { get; set; }
/// <summary>
/// Instance of the plugin.
/// </summary>
[JsonIgnore] public IPlugin Instance { get; set; }
}
public enum PluginStatus
{
Unloaded,
Initializing,
Loaded,
Disposing
}
ViewModel:
public class PluginViewModel : ViewModelBase
{
#region Properties
private ObservableCollection<Plugin> pluginList;
public ObservableCollection<Plugin> PluginList
{
get => pluginList;
set => SetProperty(ref pluginList, value);
}
private Plugin selectedPlugin;
public Plugin SelectedPlugin
{
get => selectedPlugin;
set => SetProperty(ref selectedPlugin, value);
}
private string gConsoleText;
public string GConsoleText
{
get => gConsoleText;
set => SetProperty(ref gConsoleText, value);
}
#endregion
#region ButtonCommands
private ICommand addCommand;
public ICommand AddCommand
{
get => addCommand ?? (addCommand = new CommandHandler(() => Add(), () => CanExecuteAdd));
}
public bool CanExecuteAdd
{
get => true;
}
private ICommand removeCommand;
public ICommand RemoveCommand
{
get => removeCommand ?? (removeCommand = new CommandHandler(() => Remove(), () => CanExecuteRemove));
}
public bool CanExecuteRemove
{
get => SelectedPlugin != null &&
SelectedPlugin.Status == PluginStatus.Unloaded;
}
private ICommand loadCommand;
public ICommand LoadCommand
{
get => loadCommand ?? (loadCommand = new CommandHandler(() => Load(), () => CanExecuteLoad));
}
public bool CanExecuteLoad
{
get => SelectedPlugin != null &&
SelectedPlugin.Status == PluginStatus.Unloaded;
}
private ICommand unloadCommand;
public ICommand UnloadCommand
{
get => unloadCommand ?? (unloadCommand = new CommandHandler(() => Unload(), () => CanExecuteUnload));
}
public bool CanExecuteUnload
{
get => SelectedPlugin != null &&
SelectedPlugin.Status == PluginStatus.Loaded;
}
#endregion
private string _pluginDir;
public PluginViewModel()
{
_pluginDir = Settings.PluginDir;
Manager.PluginManager.Instance.PluginLoaded += OnPluginLoaded;
Manager.PluginManager.Instance.PluginUnloaded += OnPluginUnloaded;
Manager.PluginManager.Instance.PluginRemoved += OnPluginRemoved;
this.pluginList = new ObservableCollection<Plugin>();
}
public void Add()
{
var filePath = OpenFileDialog();
if(string.IsNullOrEmpty(filePath))
return;
string pluginFile = Path.GetFileName(filePath);
if(IsAssemblyInPluginList(pluginFile))
{
ShowErrorDialog($"Plugin '{pluginFile}' is already in the List.");
return;
}
Plugin plugin = CreatePlugin(filePath);
try
{
Manager.PluginManager.Instance.Add(plugin);
}
catch(Exception e)
{
GConsole($"Error: {e.Message} ({filePath})");
ShowErrorDialog(e.Message);
return;
}
PluginList.Add(plugin);
Manager.PluginManager.Instance.Add(plugin);
SavePluginList();
}
public void Remove()
{
if(SelectedPlugin == null)
return;
Manager.PluginManager.Instance.Remove(SelectedPlugin);
PluginList.Remove(SelectedPlugin);
SavePluginList();
}
public void Load()
{
if(SelectedPlugin != null && SelectedPlugin.Status == PluginStatus.Unloaded)
try
{
Manager.PluginManager.Instance.Load(SelectedPlugin);
}
catch(TypeLoadException e)
{
string msg = $"'{Path.GetFileName(SelectedPlugin.Path)}' does not contain the class 'Plugin' in namespace '{Path.GetFileNameWithoutExtension(SelectedPlugin.Path)}'.";
ShowErrorDialog(msg);
GConsole(e.Message);
}
catch(SerializationException e)
{
string msg = $"'{Path.GetFileName(SelectedPlugin.Path)}' does not inherit from 'MarshalByRefObject' class.";
ShowErrorDialog(msg);
GConsole(e.Message);
}
}
public void Unload()
{
if(SelectedPlugin != null && SelectedPlugin.Status == PluginStatus.Loaded)
Manager.PluginManager.Instance.Unload(SelectedPlugin);
}
#region Helpers
//... Muss wieder Zeichen sparen.
}
Moin,
ich tue mir gerade schwer beim richtigen Einbinden eines Enum in eine ListView mittels MVVM. Die ListView wird vom ObservableCollection<Plugin> befüllt. Nach dem Start wird das Enum gelesen und richtig angezeigt, sowohl als Text als auch als Image via Converter.
Beim Laden und Entladen eines Plugins wird der Wert des Enum-Propertys (State) verändert. Die Veränderung wird aber nicht von der ListView registiert, aber sehr wohl von den Buttons, die jenach Value des Enums aktiviert und deaktiviert werden.
Auf dem Screenshot im Anhang sieht man es deutlicher, als ich es beschreiben kann. Das Plugin wird beim Start geladen und sowohl das Icon als auch der State sind richtig. Nachdem ich das Plugin entladen habe, reagierten die Buttons, aber nicht die ListView.
Google hilft mir leider nicht weiter, denn obwohl ich nach ListView suche, sind die meisten Treffer über Comboboxen.
Wie macht man das richtig?
XAML:
<Window x:Class="TestApp.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0, 5, 0, 0">
<ListView
Name="PluginListView"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding PluginList}"
SelectedItem="{Binding SelectedPlugin}"
SelectionMode="Single"
SizeChanged="PluginListView_OnSizeChanged" Loaded="PluginListView_OnLoaded">
<ListView.View>
<GridView>
<GridViewColumn Width="32">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Tag="{Binding State}" Width="16" Height="16" Margin="0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource Self}}" Value="Loaded">
<Setter Property="Source" Value="/Resources/green_icon.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource Self}}" Value="Unloaded">
<Setter Property="Source" Value="/Resources/red_icon.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Path" DisplayMemberBinding="{Binding ProjectPath}" Width="Auto" />
<GridViewColumn Header="State" DisplayMemberBinding="{Binding State}" Width="80"/>
<GridViewColumn Header="Auto Load" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Path=AutoLoad, Mode=TwoWay}"
HorizontalAlignment="Center"
Checked="OnCheckboxCheckChange"
Unchecked="OnCheckboxCheckChange"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Auto Reload" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Path=AutoReload, Mode=TwoWay}"
HorizontalAlignment="Center"
Checked="OnCheckboxCheckChange"
Unchecked="OnCheckboxCheckChange"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<TextBox
Text="{Binding GConsoleText}"
Name="GConsole"
VerticalAlignment="Bottom"
MinHeight="100"/>
</Grid>
<StackPanel Grid.Column="1">
<StackPanel Orientation="Horizontal" Margin="5">
<Button
Content="Add"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Padding="3"
Command="{Binding AddCommand}" />
<Button
Content="Remove"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10, 0, 0, 0"
Padding="3"
Command="{Binding RemoveCommand}" CommandParameter="Remove"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<Button
Content="Load"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Padding="3"
Command="{Binding LoadCommand}" CommandParameter="Load"/>
<Button
Content="Unload"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10, 0, 0, 0"
Padding="3"
Command="{Binding UnloadCommand}" CommandParameter="Unload"/>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Window>
(Kann leider nicht mehr Code einfügen, da ich 8k Zeichen aufgebraucht sind.)
Moin,
ich nutze für meine GUI WPF und für die Button-Commands die Klasse CommandHandler. Dafür hat VS das Framework Microsoft.VisualStudio.Uitilities installiert. Funktioniert wunderbar. Aber nach dem Debuggen kopierte VS die benütigten Dateien ins Stammverzeichnis. Jetzt sieht das so aus wie im Anhang.
Kann man VS 2022 anweisen, daß das Framework ins eigene Unterverzeichnis kopiert werden soll, damit es etwas aufgeräumter aussieht?
Ich habs raus. War das eine schwere Geburt! Es liegt nicht am Code! Und ich bin irgendwie froh drüber. Aber dein Verweis auf VS war auch richtig. Ich habe VS zweimal offen. Einmal mit der TestApp und einmal mit dem TestPlugin. Schließe ich das VS mit der TestApp und starte das Programm aus dem bin/Debug-Ordner, funktioniert der Build vom TestPlugin anstandslos. Puuuuuh! Blöderweise kommt der Fehler auch, wenn ich zwei verschiedene VS Versionen für die beiden Projekte nutze. 2019 und 2022 beißen sich auch.
Jetzt fängst an zu stochern statt strukturiert vorzugehen, das macht kein Sinn.
Du, das ist ein Hobby. Ich mache das nicht professionell. Paarmal im Jahr überkommt es mich und ich habe plötzlich eine Idee, die ich programmieren will, und dann artet es schon öfter in einer Trail & Error-Orgie aus. 🙂 Googlen, Code-Beispiele ausprobieren, noch mehr Beispiele ausprobieren, grübeln, was ich anders machen könnte, frei nach dem Motto: wenn ich nich so rum, dann andersrum..., ausprobieren, usw. Aber nichts gegen Weiterentwicklung, wenn du mir einen Hinweis gibts, wie man strukturiert Fehler sucht, versuche ich es gerne.
Ich habe jetzt nochmal paar Ideen ausprobiert, z.B. habe ich den Watcher als erstes erstellt und dann die AppDomain. Hat nicht funktioniert. Darauf hin habe ich alles einzeln auskommentiert, spricht die Elemente von AppDomain, Copy und FSW, bis zu der Stelle, an der VS den Build ohne Probleme durchführt. Funktioniert hat haben diese Mal tatsächlich AppDomain mit dem Watcher, aber ohne File.Copy (es wurde die dll geladen, die bereits im Plugin-Verzeichnis war). Das ist wieder einer dieser Momente, an dem ich geneigt bin zu glauben, daß mein Code ein Eigenleben hat. Denn, es war beim Schreiben des Codes anders. Da war es noch der FSW. Und eigentlich sollte das Lesen einer Datei nichts locken. Wie auch immer.
Ich habe File.Copy durch FileStream ersetzt um zu schauen, ob sich etwas ändert.
//File.Copy(fileFrom, fileTo, overwrite: true);
using(var streamFrom = new FileStream(
fileFrom,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite))
{
using(var streamTo = new FileStream(fileTo, FileMode.Create))
{
var buffer = new byte[0x10000];
int bytes;
while((bytes = streamFrom.Read(buffer, 0, buffer.Length)) > 0)
{
streamTo.Write(buffer, 0, bytes);
}
}
}
Leider ändert sich am Verhalten nichts.
Dann kam mir die Idee, das Kopieren async durchzuführen, um wirklich sich er zu sein, daß der Kopiervorgang auch abgeschlossen wurde.
//File.Copy(fileFrom, fileTo, overwrite: true);
CopyFileAsync(fileFrom, fileTo).Wait();
...
private async Task CopyFileAsync(string sourcePath, string destinationPath)
{
using(Stream source = File.OpenRead(sourcePath))
{
using(Stream destination = File.Create(destinationPath))
{
await source.CopyToAsync(destination);
}
}
}
Hehe, beim Klick auf den Load Button friert die GUI ein und es tut sich nichts (dachte immer, async + await sollen genau das verhindern). In der Konsole wird auch nichts mehr ausgegeben, d.h. das Programm kommt erst gar nicht so weit die dll zu laden. Ich habe vorher nicht versucht ein Build zu erstellen, die GUI friert immer und bei jedem Versuch ein.
Was das Plugin angeht, ja, es hat noch kein Dispose. Es besteht aus reinen TestMethoden. Alles andere kommt erst in den nächsten Schritten.
public class MyPlugIn : MarshalByRefObject, IPlugIn
{
public void DoSomething()
{
Console.WriteLine("working...");
}
string IPlugIn.DoSomethingElse(string message)
{
Console.WriteLine(message);
return "Something else done...";
}
}
Das ist mir schon bewusst, daß es am Code liegt. Aber am welchen Teil des Codes? Am Dispose kann es nicht wirklich liegen, da FSW während des Build-Vorgangs noch läuft und laufen muss, damit er eben diesen registriert und feuert. Dispose geschieht erst danach. Das Builden läuft aber schon nach dem ersten Laden der Dll nicht rund, also bevor der FSW das erste Mal disposet wird.
Ich habe Dispose() tatsächlich nicht benutzt. Habe es eingefügt und es ändert sich leicht etwas. Beim ersten Mal kann ich nicht builden, auch, wenn ich eine Minute lang warte, bevor ich builde. Drücke ich gleich danach nochmal F6, geht das. Entlade und lade ich die AppDomain, fängt das Spiel von vorn an. Er buildet beim zweiten Mal auch, wenn ich gleich nach dem Laden der dll zweimal builde.
Ich habe aber eben herausgefunden, daß wenn ich die .pdb-Datei im bin/Debug lösche, buildet er auch beim ersten Mal. Scheinbar wird die Blockade aufgehoben, wenn sich etwas im Verzeichnis ändert. Erstelle ich nach dem Aufrufen des Watchers eine leere Text-Datei im bin/Debug und warte paar Sekunden, kann ich beim ersten Mal builden oder ich builde zweimal. Aber nach dem Reload fängt auch das nochmal an. Wahrscheinlich braucht der Watcher etwas bis er disposet.
public partial class MainWindow : Window
{
private Contracts.IPlugIn _plugin;
private AppDomain _domain;
private FileSystemWatcher _watcher;
public MainWindow()
{
// For english exceptions
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");
InitializeComponent();
}
private void BtnLoadPlugin_Click(object sender, RoutedEventArgs e)
{
string fileFrom = "J:\\workspace\\_csharp\\Projects\\_test\\TestPlugIn\\bin\\Debug\\TestPlugIn.dll";
string fileTo = "J:\\workspace\\_csharp\\Projects\\_test\\PlugInManager\\TestApp\\bin\\Debug\\PlugIns\\TestPlugIn.dll";
File.Copy(fileFrom, fileTo, overwrite: true);
_domain = AppDomain.CreateDomain("TestPlugIn", AppDomain.CurrentDomain.Evidence, new AppDomainSetup
{
ApplicationName = "TestPlugIn",
ApplicationBase = "J:\\workspace\\_csharp\\Projects\\_test\\PlugInManager\\TestApp\\bin\\Debug\\PlugIns",
});
_plugin = (IPlugIn)_domain.CreateInstanceAndUnwrap("TestPlugIn",
$"{"TestPlugIn"}.{"MyPlugIn"}");
_plugin.DoSomething();
string msg = _plugin.DoSomethingElse("Do Something else...");
Console.WriteLine(msg);
_watcher = new FileSystemWatcher("J:\\workspace\\_csharp\\Projects\\_test\\TestPlugIn\\bin\\Debug\\")
{
NotifyFilter = NotifyFilters.LastWrite,
};
_watcher.EnableRaisingEvents = true;
_watcher.Changed += OnFolderChanged;
string workaround = "J:\\workspace\\_csharp\\Projects\\_test\\TestPlugIn\\bin\\Debug\\workaround.txt";
File.Create(workaround).Dispose();
}
private void BtnUnloadPlugin_Click(object sender, RoutedEventArgs e)
{
AppDomain.Unload(_domain);
_watcher.Changed -= OnFolderChanged;
_watcher.Dispose();
_watcher = null;
}
private void OnFolderChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"File Changed: '{e.Name}'.");
}
}
Ich verstehe aber nicht, was das alles bin mit obj/Debug Verzeichnis zu tun hat und warum der FSW in einem eigenen Projekt, ohne das Laden und Entladen von AppDomains, keinen Einfluss auf VS hat und ich builden kann, obwohl er permanent das bin/Debug-Verzeichnis überwacht.
Moin,
ich möchte .dlls dynamisch laden und entladen und habe mich an der ersten Anwort in diesem Post orientiert.
Das Programm kopiert eine Dll aus dem Debugverzeichnis eines anderen Projekts (Plugin), läd die Dll und schaltet den FileSystemWatcher ein, der das bin/Debug-Verzeichnis des Plugin-Projekts überwacht. Wenn ich das Projekt builde und die Dll überschrieben wird, soll der Watcher feueren. Am Ende soll das Entladen, Kopieren und Laden der Dll aus dem Debug-Verzeichnis automatisch ablaufen. Momentan benutze ich noch für das LAden und Entladen Buttons.
Das ist der Code:
private void BtnLoadPlugin_Click(object sender, RoutedEventArgs e)
{
string fileFrom = "J:\\workspace\\_csharp\\Projects\\_test\\TestPlugIn\\bin\\Debug\\TestPlugIn.dll";
string fileTo = "J:\\workspace\\_csharp\\Projects\\_test\\PlugInManager\\TestApp\\bin\\Debug\\PlugIns\\TestPlugIn.dll";
File.Copy(fileFrom, fileTo, overwrite: true);
_domain = AppDomain.CreateDomain("TestPlugIn", AppDomain.CurrentDomain.Evidence, new AppDomainSetup
{
ApplicationName = "TestPlugIn",
ApplicationBase = "J:\\workspace\\_csharp\\Projects\\_test\\PlugInManager\\TestApp\\bin\\Debug\\PlugIns",
});
_plugin = (IPlugIn)_domain.CreateInstanceAndUnwrap("TestPlugIn",
$"{"TestPlugIn"}.{"MyPlugIn"}");
_plugin.DoSomething();
string msg = _plugin.DoSomethingElse("Do Something else...");
Console.WriteLine(msg);
_watcher = new FileSystemWatcher("J:\\workspace\\_csharp\\Projects\\_test\\TestPlugIn\\bin\\Debug\\")
{
NotifyFilter = NotifyFilters.LastWrite,
};
_watcher.EnableRaisingEvents = true;
_watcher.Changed += OnFolderChanged;
}
private void BtnUnloadPlugin_Click(object sender, RoutedEventArgs e)
{
AppDomain.Unload(_domain);
_watcher.Changed -= OnFolderChanged;
}
private void OnFolderChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"File Changed: '{e.Name}'.");
}
Das Laden und Entladen der AppDomain und der Dll funktioniert auch soweit. Leider führt das Überwachen des FileSystemWatchers irgendwie dazu, daß ich das Projekt, das vom Watcher überwacht wird, nicht builden kann.
Fehlermeldung beim Build-Versuch:
Fehlermeldung:
Cannot open 'J:\workspace_csharp\Projects_test\TestPlugIn\obj\Debug\TestPlugIn.pdb' for writing -- 'The process cannot access the file 'J:\workspace_csharp\Projects_test\TestPlugIn\obj\Debug\TestPlugIn.pdb' because it is being used by another process.'
Ich brauchte eine Weile bis ich die Fehlermeldung richtig gelesen habe. Es handelt sich nicht um bin/Debug, sondern um obj/Debug. Das Verzeichnis obj/Debug wird nirgends im Code benutzt. Die beiden Projekte kannen sich nicht. Die einzige Verbindung zwischen den beiden ist das Interface IPlugin, deren Dll beide Projekte als Referenz haben.
Ich habe im Zuge meiner Recherche bereits erfahren, daß der FileSystemWatcher weder Verzeichnisse noch Dateien blocken kann. ABER, lasse ich ihn weg, lade mit dem Programm die Dll und versuche dann das Plugin-Projekt zu builden, dann klappts. Auf der anderen Seite, wenn ich nur den Wachter in einem separatem Projekt starte, kann ich builden wie ich will, läuft auch ohne Probleme. Ich komme einfach nicht dahinter, woran es liegt, daß beides zusammen nicht funktionert, aber einzeln schon.
Danke dir. Von Avalonia nehme ich lieber Abstand. Das habe ich vor dem Upgrade benutzt und kann mich erinnern, daß ich eine Ewigkeit dran saß um es zum Laufen zu bekommen.
Uno zu installieren funktionierte fast auf Anhieb. 👍
Moin,
ich hab mein Linux Mint auf v21.1 upgegradet und musste dafür .net core deinstallieren. Jetzt habe ich .net wieder eingerichtet und gleich die neuste Version .net 7 genommen. Leider finde ich aber kein GUI Framework für. Gibt es schon was für .NET 7 unter Linux? Am liebsten wäre mir Qt, aber ich finde nur was für Windows.
Ok, das hat mich jetzt sehr überrascht. Aber nach einem Blick, tief in meine Projektkiste, habe ich die Lösung des Rätsels gefunden. Erstmal: ja, ihr habt Recht, SQLite war auch schon früher mit ADO.net, und auch von der Code-Struktur her gleich.
Ich habe mir irgendwann einen kleinen Wrapper geschrieben, benutzte dann SQLite immer auf diese Weise und habe irgendwann vergessen, daß es ein Wrapper war. Jetzt, nach längerer Zeit, suchte ich nach meinen Geistern, die ich für SQLite hielt. 🙂
Moin,
ich habe schon etwas länger keine Datenbanken verwendet. Jetzt brauche ich eine für ein kleines CRUD Projekt. Meine Wahl fiel instinktiv auf SQLite. Leider finde ich die Version nicht mehr, die ich gewohnt war.
Was ich meine, sieht man hier auf der Microsoft-Seite:
https://learn.microsoft.com/de-de/dotnet/standard/data/sqlite/?tabs=netcore-cli
Als ich das letzte Mal SQLite benutzt habe, hatte ich einen "normalen" SQL-String, den ich mit C#-Variablen dynamisch füllte. In der Microsoft Version muss man jetzt Commands kreiren, sie mit API spezifischen Variablen füllen, diese "Parameter" dann noch spezifizieren und anschießend das ganze ausführen. Viel zu umständlich für das, was ich vorhabe.
Wenn ich das richtig verstanden habe, basiert das auf ADO.net.
In VS Code wird im Nuget Package Manager als erstes Ergebnis SQLite 3.12.3 angezeigt. Aber diese Version kommt ohne Referenzen. Die Version System.Data.SQLite hingegen ist wie die Microsoft.Data.SQLite aufgebaut, also auch nicht das, was ich suche.
Gibt es das "normale" SQLite noch, ohne ADO.net?
Danke für die Tipps. Das sind sehr interessante Sachen bei. Muss mir das alles aber nochmal genauer anschauen.
Dachte die Projekte auf Github sind immer öffentlich. Scheint jetzt anders zu sein und wäre die leichteste Lösung, denke ich. Aber ein SeaFile Server auf einem RasPi ist auch ein sehr verführerischer Gedanke. 😁
Zur Versionskontrolle:
Jaaa, habe ich eigentlich auch seit Jahren auf meiner imaginären ToDo-Liste. 🙂 Mir ist auch bewusste, ich sollte das mal lernen. Das Ding ist aber, meine Projekte sind so klein, ich hatte nie den Bedarf eine Version zurückzuspringen, und schiebe es ständig vor mich hin.
Moin,
ich möchte meine Projekte gerne zusätzlich zum lokalem Arbeitsverzeichnis auch online speichern. Ich laß etwas über das Thema und merkte schnell, es gibt viele Möglichkeiten. Fühle mich gerade etwas vom Thema erschlagen. Hat jemand darin Erfahrung und könnte mir einen Tipp geben, wonach ich konkret suchen sollte?
Momentan speichere ich alle meine Daten auf einen USB-Stick und arbeite mal am Desktop-PC und mal am Notebook. Auf beiden läuft sowohl Linux Mint als auch Windows 10. Auf beiden System wird auch programmiert. Oft vergesse ich den Stick zu Hause und wünschte mir dann, ich könnte den aktuellen Stand online runterladen.
Zu Verfügung stehen mir ein eigener (klassischer) Webspace - kein Server, den ich konfigurieren oder etwas drauf installieren könnte - den ich via FTP als Speicherplatz zweckentfermden könnte und Google cloud. Ich möchte es so simple wie möglich halten. Heißt, es soll automatisch hoch- und runtergeladen werden. Versionskontrolle ist nicht nötig. Es genügt mir, wenn die aktuelle Version immer online ist und die lokalen Daten überschrieben werden.
Ich nutze VS Code, da gibt es Remote SSH für, aber ich habe ja keinen Server. Vielleicht gibt es aber was anderes dafür?
Ok, das hat funktioniert. Vielen Dank. Habe nur die Zeile
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
hinzugefügen müssen. Kannte ich so aber auch noch nicht.
Tue mir noch etwas schwer. Ist mein erstes C#-Projekt unter Linux und mit VS Code.
In dem Code kommt Color.FromRPG von Avalonia.Media.
Naja, soweit war ich auch schon...
Nun, ich nutze nicht .Net Core 3.0 sondern v6. Alles vorgestern frisch installiert. Muss ich trotzdem jetzt 3.0 installieren?
Moin,
ich versuche gerade den ColorTranslator zu benutzen und laufe in einen Fehler, den ich nicht wegbekomme.
The type 'ColorTranslator' exists in both 'System.Drawing.Common, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' and 'System.Drawing.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' [WerkstattOrganizer]csharp(CS0433)
Das ist der Code:
using Avalonia.Media;
namespace WerkstattOrganizer
{
public static class Utils
{
public static Color HexToColor(string hex)
{
var color = System.Drawing.ColorTranslator.FromHtml(hex);
return Color.FromRgb(color.R, color.G, color.B);
}
}
}
Das Ding ist jetzt, wenn ich "System.Drawing.Common.ColorTranslator" oder "System.Drawing.Primitives.ColorTranslator" nehme, wird mir gesagt, daß diese Namespaces nicht existieren. Wie wähle ich jetzt eins von beiden aus?
Ich habe das nicht programmiert, ich benutze es nur. 🙂 Es ist ein open-source Client für das uralte Ultima Online, geschrieben in C#. ClassicUO nennt er sich und das Plugin, das ich dazu benutze, ist der ClassicAssist.
@Abt:
Ja, das ist etwas komplizierter, als ich es anfangs beschrieben habe. Die eigentliche Ursache liegt in einem Plugin. Ohne das Plugin wurde die Exception nicht geworfen. Irgendwo beißen sich die beiden unter bestimmten Umständen. Nun, das Plugin ist einerseits auch open-source, andererseits hat es mehrere Hundert Klassen. Da die Exception auf den Client verweist, weiß ich nicht wo ich im Plugin suchen sollte. Alleine das Einlesen würde Wochen dauern, die Fehlersuche womöglich Monate.
Von daher ist es jetzt für mich das Naheliegendste die Exception zu umgehen, da die Methode eh nur zur Berechnung einer Amimation von Spielfiguren dient. Ich kann damit leben, wenn mal eine Figur eine Animation nicht mitmacht. Nur der Client sollte nicht dabei crashen. 🙂
@Th69:
Ehrlich gesagt: keine Ahnung. 🙂 Mit Memory und Pointern habe ich mich noch nicht auseinandergesetzt. Ich weiß nur, daß der >>-Operator irgendwas mit verschieben bedeutet, aber ausrechnen und/oder überprüfen kann ich die Rechnung im Code nicht.
Habe ich das richtig verstanden, daß wenn ich das Attribut [HandleProcessCorruptedStateExceptions] hinzufüge, dann sollte die Exception gecatcht werden? Leider kann ich die Exception nicht ohne Weiteres rekonstruieren und überprüfen ob es jetzt klappt. Die wird einfach ab und zu geworfen.
Ist das so richtig?
[HandleProcessCorruptedStateExceptions]
public unsafe AnimDataFrame CalculateCurrentGraphic(ushort graphic)
{
IntPtr address = _file?.StartAddress ?? IntPtr.Zero;
if(address != IntPtr.Zero)
{
try
{
IntPtr addr = address + (graphic * 68 + 4 * ((graphic >> 3) + 1));
ref AnimDataFrame a = ref Unsafe.AsRef<AnimDataFrame>((void*)addr);
return a;
}
catch(System.AccessViolationException e)
{
System.Console.WriteLine(e.Message);
return default;
}
}
return default;
}
Sorry, mein Edit und Dein Post haben sich überschnitten.
Wenn man MemoryExceptions nicht abfangen kann, gibt es dann wenigstens eine Möglichkeit zu überprüfen, ob auf jene Stelle im Memory gerade zugegriffen werden kann?
Moin,
ich habe mir ein Open-Source-Projekt runtergeladen und bekomme da ab und zu eine Exception geworfen. Dachte, ich packe einfach an der Stelle einen try-catch Ausdruck und gut ist. Leider funktioniert das nicht. 🙂 Da ich selber eigentlich sehr selten try-catch benutze, tue ich mir gerade etwas schwer das richtig zu setzen.
Der Ausnahmefehler kommt immer bei return a, ob mit oder ohne try.
Im Original sieht die Methode wie folgt aus:
public unsafe AnimDataFrame CalculateCurrentGraphic(ushort graphic)
{
IntPtr address = _file?.StartAddress ?? IntPtr.Zero;
if(address != IntPtr.Zero)
{
IntPtr addr = address + (graphic * 68 + 4 * ((graphic >> 3) + 1));
ref AnimDataFrame a = ref Unsafe.AsRef<AnimDataFrame>((void*)addr);
return a;
}
return default;
}
Das ist mein try-catch Versuch:
public unsafe AnimDataFrame CalculateCurrentGraphic(ushort graphic)
{
IntPtr address = _file?.StartAddress ?? IntPtr.Zero;
if(address != IntPtr.Zero)
{
try
{
IntPtr addr = address + (graphic * 68 + 4 * ((graphic >> 3) + 1));
ref AnimDataFrame a = ref Unsafe.AsRef<AnimDataFrame>((void*)addr);
return a;
}
catch(System.AccessViolationException e)
{
System.Console.WriteLine(e.Message);
return default;
}
}
return default;
}
Ist das so richtig?
Edit:
Habe die Exception ganz vergessen.
Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. Dann der Verweis auf die Zeile mit return a.
Moin, ich möchte ein Diagramm programmieren, das live mit Daten gefüttert wird, 30 mal in der Sekunde. Außerdem ist die Zeitachse auch live, d.h.
ältere Daten sollen aus dem Diagramm herausscrollen.
Was nehme ich dafür?
Canvas war meine erste Idee. Aber macht es Sinn ein Canvas mit Hunderten von Children zu fluten?
Oder doch lieber mit Bitmap arbeiten. Aber da weiß ich nicht, ob der GUI-Thread da mitkommt. Der wird jetzt schon etwas beansprucht.
Oder gibt es da noch eine ganz andere Möglichkeit?
Ahja, das ganze ist in WPF. Wobei auch hier die Frage: bietet C# für grafische Darstellungen etwas besseres an als WPF?
Moin,
Ich habe eine grafische Drehzahlmesseranzeige mit einem transparten Bereich, in einem Grid, mit den Ausmaßen der Anzeige (Ziffernblatt). Der Anzeigebereich hat die Form eines Halbkreises und wird, je nach Stand, mit einem sich hineindrehenden Image "gefüllt". Funktioniert auch soweit.
Das Problem ist, das Image soll, wenn es außerhalb des Grid ist, nicht angezeigt werden. Wird es aber. 😃 Wenn ich das Image nicht drehe, sondern in der Position verändere, dann verschwindet der Teil, der nicht im Grid ist.
Scheinbar wird bei der Darstellung nur auf die Position (Margin) geachtet, aber nicht die Rotation. Meine Frage wäre: wie lasse ich ein Image aus dem sichtbaren Bereich eines Grid herausrotieren?
Das hier ist der Code für die Rotation:
public double NeedlePos
{
get { return needleRotation.Angle; } // TODO: Type
set { this.NeedleImage.Dispatcher.Invoke(() => {
switch (GaugeType)
{
case Type.CIRCULAR:
this.needleRotation.Angle = value;
break;
case Type.LINEAR_HORIZONTAL:
needleImage.Margin = new Thickness(value, needleImage.Margin.Top, 0, 0);
break;
case Type.LINEAR_VERTICAL:
needleImage.Margin = new Thickness(needleImage.Margin.Left, value, 0, 0);
break;
}
});
}
}
public GaugeUI() : base()
{
this.backgroundImage = new Image();
this.needleImage = new Image();
this.NeedlePivot = new Point(0, 0);
this.needleRotation = new RotateTransform();
TransformGroup transformGroup = new TransformGroup();
transformGroup.Children.Add(needleRotation);
this.NeedleImage.RenderTransform = transformGroup;
this.GaugeType = Type.CIRCULAR;
}
Ja, fand das mit Redex etwas eleganter. 😃 Aber jetzt komme ich nicht drum herum es mit den String-Funktionen zu lösen. Mir ist aufgefallen, daß die Namen auch Space-Zeichen enthalten können. Somit muss ich alles von @ bis zur Cursor-Position auslesen.
Wie lese ich nun den Index des Cursors aus? Geht das überhaupt?
Und btw. der leere String lang an einem Fehler im ViewModel. Von daher, Redex war gar nicht so falsch, bis auf den fehlenden Ausdruck des Stringendes. Aber das nützt mir ja jetzt auch nichts mehr.
Moin,
ich quäle mich gerade mit redex ab und brauche etwas Hilfe. 😃
Ich habe eine Liste mit Chat-Usern, bzw deren Namen. Wenn ich in das Input-Feld ein @ schreibe, poppt eine Liste mit den Namen auf. Mit [Tab] gibts dann die Autovervollständigung.
Nun versuche ich gerade alles hinterm @ und einem Space oder dem Ende des Strings auszulesen, um die Namensliste anzupassen.
Naja, ich krieg da immer einen leeren String raus. 😃
if(this.viewModel.ChatGlobalText != string.Empty)
{
Regex rx = new Regex(@"\@(.*?)\s");
string startsWith = rx.Match(this.viewModel.ChatGlobalText + " ").Value;
this.UpdatePlayerNameList(startsWith);
}
Ich setzte da ein "künstliches" Space hinter dem String, da es ansonsten kracht. Ich weiß nicht, wie man "oder das Ende des Strings" ausdrückt.
Hoffe, jemand kann mir helfen.
Das funktioniert. Vielen dank.
Moin,
ich habe eine Liste mit dem Datentyp einer Base-Klasse und möchte überprüfen, ob sich ein bestimmtes vererbtes Objekt dieser Base-Klasse in der Liste befindet.
Zum Beispiel:
List<BaseTask> taskList = new List<BaseTask>();
// TaskA, TaskB und TaskC erben von BaseTask.
taskList.Add(new TaskA());
taskList.Add(new TaskB());
taskList.Add(new TaskC());
Bin da etwas ratlos und habe es wie folgt versucht:
if(taskList.Find(task => task.GetType().Name == "TaskA") != null)
// do something
Naja, es klappt nicht. Gibt es da eine andere Möglichkeit?
Moin,
ich verschwende sehr viel Zeit damit passende Namen für Namespaces und Klassen zu finden. Habt ihr einen "Trick", mit dem man sich das einfacher machen kann?
Vor allem bei kleinen Methoden, wie die Folgenden, fällt mir nichts ein:
public static int GenerateID()
{
var now = DateTime.Now;
var date = DateTime.MinValue.AddHours(now.Hour).AddMinutes(now.Minute).AddSeconds(now.Second).AddMilliseconds(now.Millisecond);
return (int)(date.Ticks / 10000);
}
... oder...
public static bool IsNumeric(string str)
{
// wird benütigt, da TryParse eine Referenz verlangt
float output;
// versucht den String in ein Float zu parsen und gibt zurück ob der Versuch erfolgreich war
return float.TryParse(str, out output);
}
In welche Klasse packt ihr solche Methoden, die quasi von überall her angesprochen werden können? Aus Ratlosigkeit habe ich meine Klasse "Helpers" genannt und da alles reingetan.
Hoffe, ihr könnt mir weiterhelfen, denn für die ganze Namensfinderei gehen bei mir Stunden drauf.
Nun gehts.
private int GetInt(string command)
{
lock(this)
{
UODLL.SetTop(UOHandle, 0);
UODLL.PushStrVal(UOHandle, "Get");
UODLL.PushStrVal(UOHandle, command);
var result = UODLL.Execute(UOHandle);
if(result == 0)
return UODLL.GetInteger(UOHandle, 1);
else
return 0;
}
}
Frage mich aber, warum [MethodImpl(MethodImplOptions.Synchronized)] bei mir nicht funktioniert hat. Fände diese Lösung eleganter.
Hab ich doch. 😄 Aber wie gesagt, [MethodImpl(MethodImplOptions.Synchronized)] funktioniert nicht. Und lock(GetInt) kommt nicht mal an den Compiler heran, wird rot unterstrichen.
Moin, mein Programm crasht, wenn ich mit zwei Threads auf eine DLL zugreife und ich kriege das nicht hingebogen. Ist alles noch trail & error, was ich hier mache. 😃 Es handelt sich hierbei um ein Wrapper, der ursprünglich in Java geschrieben und später von jemandem in C# umgeschrieben wurde.
[MethodImpl(MethodImplOptions.Synchronized)]
private int GetInt(string command)
{
UODLL.SetTop(UOHandle, 0);
UODLL.PushStrVal(UOHandle, "Get");
UODLL.PushStrVal(UOHandle, command);
var result = UODLL.Execute(UOHandle);
if (result == 0)
return UODLL.GetInteger(UOHandle, 1);
else
return 0;
}
[MethodImpl(MethodImplOptions.Synchronized)]
private void SetInt(string command, int value)
{
UODLL.SetTop(UOHandle, 0);
UODLL.PushStrVal(UOHandle, "Set");
UODLL.PushStrVal(UOHandle, command);
UODLL.PushInteger(UOHandle, value);
UODLL.Execute(UOHandle);
}
So sehen die Methoden aus. [MethodImpl(MethodImplOptions.Synchronized)] kam von mir, bewirkt leider nichts. Kracht nach wie vor an selber Stelle.
Im Java-Original sieht das so aus:
public synchronized void pushInteger(int value) {
game.PushInteger(handle, value);
}
public synchronized int getInteger(int index) {
return game.GetInteger(handle, index);
}
Was ist C#'s Gegenstück zu Javas "synchronized"?
Ok, es klapp. Nicht so ganz, wie im Beispiel mit zwei ViewModels, aber mit einem geht das. Auf jeden Fall hat es mein Hidden Value Problem gelöst, da ich vom SelectedItem das entsprechende Objekt zurückbekomme und nicht nur einen Listenindex. 😃
Drei-Schichten-Architektur: Schreibe gerade alles drauf um.
MVVM: Ist drin. Für Listen nutze ich es aber nicht, da ich die Listen nicht über MVVM zum Laufen bekomme. Im Artikel der Punk 2.4, wie befülle ich da die Liste? Muss ich für jeden Listeneintrag ein Objekt von EmployeeViewModel instanziieren? Diesen Datentyp erwartet nämlich viewModel.Employees.Add.
Moin,
kann man irgendwie eine Art Hidden Value an die ListBox Elemente anhängen, wo ich eine ID hinterlegen kann, ähnlich wie im HTML <input name="xyz" value="test" hidden="1234">?
Ich fülle die Liste mit Personen aus einer Datenbank. Die Liste zeigt mir Vorname und Nachname an. Nun gibt es zwei Hans Müller. Will jetzt wissen, welchen von beiden ich auswähle. Am besten wäre es, wenn ich die ID aus der DB an jedes Element übergeben könnte.
Geht das irgendwie?
Haha, Exceptions! Da war ja mal was. 😄 Die hatte ich überhaupt nicht auf dem Radar (obwohl ich ständig welche um die Ohren geworfen bekomme). Ok, notiert!
Das löst mein Problem aber noch nicht vollständig, da Exceptions nur für Fehler und andere Ausnahmen gedacht sind. Was ist aber mit Statusmeldungen? Ich schreibe die gerade auf Events um, aber irgendwie gefällt mir das auch noch nicht ganz. Ist es üblich ein Event durch ein anderes Event zu invoken? Anders komme ich nicht durch die Klassen an die GUI.
Die Youtube-Kanäle sehen sehr interessant aus, aber leider brauche ich alles, aufgrund meines Gehörs, in Textform.
Danke für eure Antworten. Jetzt habe ich noch mehr Fragen. 😃
Habe mir Rx angeschaut aber auch das 3-Schichten-Modell und denke, ich sollte zuerst meinen Code strukturieren bevor ich mich Rx zuwende.
Als ich Artikel zum 3-Schichten-Modell mit ihren kleinen Beispiel-Codes gelesen habe, dachte ich mir, es sei im Grunde einfach. Doch wenn ich dieses Modell auf meinen Code, bzw meine Gedankengänge übertragen möchte, wird es plötzlich kompliziert.
Die nachfolgende Situation/Frage ist exemplarisch dafür, warum ich auf globale Variablen zurückgreife:
Sagen wir, die Daten-Schicht greift auf einen SQL-Server im Netz zu. Dieser geht down und ist nicht erreichbar. Jetzt soll eine Nachricht in der GUI erscheinen a la "Daten-Server down!". Wie kommt diese Nachricht durch die Schichten zur GUI? Oder ich möchte, daß gewisse Vorgänge in der Logik-Schicht eine Nachricht ausgeben. Wie macht man das?
Wenn ich das 3-Schichten-Modell richtig verstanden habe, hat es die GUI-Schicht nicht zu interessieren wie und was die Logik-Schicht macht. Die GUI-Schicht erwartet nur ein Ergebnis. Genauso sieht es mit der Beziehung zwischen der Logik- und der Data-Schicht aus. Die Logik-Schicht weiß nicht woher die Data-Schicht ihr Daten bekommt. Wie bekomme ich also die Fehlermeldung (bzw einen Fehlercode) durch die Klassen zur GUI-Schicht?
Als einziges fällt mir nur ein, eine static Klasse zu schreiben, nennen wir sie "Console", ihr beim Start die Instanz des Labels, in dem die Nachrichten angezeigt werden, zu übergeben, und mittels Console.ShowMessage("Ging nicht/Ging doch/ etc.") von überall im Code darauf zuzugreifen. Damit kennt der gesamte Code die GUI-Schicht, bzw ein Element der GUI, was nicht sein soll (wenn ich es richtig verstanden habe). Wie löst man das?
Ich bekomme ich eine Liste mit Servernachrichten. Jede neue Nachricht wird am Ende der Liste eingefügt und ich bekomme immer nur die volle Liste. Relevant sind aber immer nur die neuen Nachrichten, die noch nicht überprüft wurden.
Jetzt wollte ich eine Klasse schreiben, mit der ich mir den Zugriff vereinfache und überlege, wie ich das mache. Da ich von mehreren Stellen des Programms auf die Nachrichten zugreife, brauche ich einen Zwischenspeicher für den letzten aufgerufenen Index, quasi den letzten List.Count. Das Naheliegendste erschien mir eine static Variable.
Moin,
bin gerade am überlegen, wie ich eine static Variable handhabe.
Zum einen kann ich sie gleich definieren:
public static class StaticClass
{
public static int myStaticInt = 0;
}
oder ich arbeite mit get und set und setze sie beim Start in MainWindow():
public static class StaticClass
{
public static int myStaticInt { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
StaticClass.myStaticInt = 0;
}
}
Für den weiteren Verlauf des Programms ist die Art, wie ich es mache, egal, da ich sie immer nur noch mit StaticClass.myStaticInt aufrufen bzw mit StaticClass.myStaticInt = Wert setzen werde.
Geht es hier mehr als nur um Programmierästhetik? Und welche Form ist die "richtige"?
Wie gesagt, ich möchte herausfinden, ob die Methode mit Screenshots Informationen zu sammeln überhaupt praktikabel ist. Ich kann mir schon denken, daß es in C++ schneller geht, nur wie viel schneller? Reicht es, um diesen Ansatz zu verfolgen, oder würde er auch in C++ scheitern?
Benchmark.NET schaue ich mir gerne an.
Ob es andere Möglichkeiten gibt, in C# Screenshots zu erstellen, weiß ich nicht. Habe mir viele Beispiele angeschaut, da ich es nicht mit der Bitmap-Klasse machen wollte, fand aber nur diesen einen Weg.