Unter Anderem für eine Unterstützung von mehreren Sprachen habe ich folgendes implementiert:
Nun muss ich aber für ganz viele GUI Klassen eine eigene Klasse implementieren, die praktisch alle gleich aussehen
public interface ILanguageText
{
void SetText();
}
public class LanguageLabel(Label label, string[] texts) : ILanguageText
{
readonly Label label = label;
readonly string[] texts = texts;
public void SetText()
{
label.Text = texts[Language.Actual] + ExtraText;
}
}
public class LanguageGroupBox(GroupBox groupBox, string[] texts) : ILanguageText
{
readonly GroupBox groupBox = groupBox;
readonly string[] texts = texts;
public void SetText()
{
groupBox.Text = texts[Language.Actual];
}
}
public class LanguageMenuItem(ToolStripMenuItem menuItem, string[] texts) : ILanguageText
{
readonly ToolStripMenuItem menuItem = menuItem;
readonly string[] texts = texts;
public void SetText()
{
menuItem.Text = texts[Language.Actual];
}
}
Kann man so etwas nicht auch mit einem Template machen? Nach meinem aktuellen Wissen geht das nicht, da die Text Eigenschaft der einzelnen Klassen ja nicht in einem gemeinsamen Interface oder einer gemeinsamen Basisklasse definiert ist.
Prinzipiell könnte das so aussehen. Aber t.Text geht ja nicht
public class LanguageItem<T>(T t, string[] texts) : ILanguageText
{
readonly T t = t;
readonly string[] texts = texts;
public void SetText()
{
t.Text = texts[Language.Actual];
}
}
Geht das vielleicht doch?
Vielleicht dann noch mal eine Verständnisfrage:
Ich habe zuerst ja als Interface IEnumerable<IClass> angegeben. Visual Studio hat mir das meine ich so vorgeschlagen. Dann brauchte ich aber zwei Funktionen in meiner Implementierung
public IEnumerator<IClass> GetEnumerator() {...}
public IEnumerator GetEnumerator() {...}
Beide Funktionen hatten dann auch die gleiche Implementierung.
Ich habe es nun mal auf
public interface ISubClass : IEnumerable
geändert. Dann benötige ich auch nur noch die eine GetEnumerator Funktion.
Es funktioniert beides.
Kann es sein, dass eine "Vererbung" von IEnumberable<IClass> dann auch nur ein foreach für IClass zulässt? Ohne die Einschränkungen könnte ich aber auch noch über andere Typen enumerieren?
Da ich aber nur eine Liste in meiner Klasse habe, ist das ja ganz egal. Mit der Spezialisierung auf <IClass> musste ich ja auch noch eine "allgemeine Enumerator Funktion" (also IEnumerator GetEnumerator). Somit bringt es mir keine Vorteile, direkt das <IClass> anzugeben. Richtig?
Wenn ich allerdings in eine "Hyperklasse" zwei Listen mit unterschiedlichen Typen hätte, könnte ich IEnumerable<Typ1> und IEnumerable<Typ2> bei meiner Klasse als zu implementierende Interface angeben. Dann benötige ich zwei entsprechende GetEnumerator Funktionen, die verschiedene Enumeratoren zurückgeben würden. Somit könnte ich dann vom gleichen Objekt unterschiedlichen foreach mit unterschiedlichen Typen machen. Ein foreach (var x in objekt) dürfte dann aber nicht mehr funktionieren.
Das ist ja noch besser.
Generell macht das sogar Sinn. Gut zu wissen.
Danke.
Da hätte ich auch selber drauf kommen können.
Hallo,
ich habe ein wirkliches Basisproblem. Ich habe gerade meinen ersten eigenen Enumerator implementiert. Das mache ich, damit ich foreach von meiner Klasse benutzten kann.
Es ist eine klassisches Item/SubItem Implementierung
public interface IClass
{
public interface ISubClass : IEnumerable<IClass>
{
int Count { get; }
IClass this[int index] { get; }
void Add(string name);
void remove(string name);
void Clear();
}
}
internal class MyClass : IClass
{
public class SubClassImpl : IClass.ISubClass
{
public readonly List<MyClass> Items = []
public IEnumerator<IClass> GetEnumerator()
{
return new MyClassEnumerator(Items);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new MyClassEnumerator(Items);
}
}
class MyClassEnumerator(List<MyClass> items) : IEnumerator<IClass>
{
readonly List<Class> items = items;
int position = -1;
public IClass Current => items[position];
object IEnumerator.Current => items[position];
public void Dispose()
{
}
public bool MoveNext()
{
if (position >= items.Count) return false;
position++;
return true;
}
public void Reset()
{
position = -1;
}
}
Wenn ich aber nun eine leere Liste habe, bekomme ich in der forEach Schleife eine Excpetion, da im enumerator auf Index -1 (der Positions Wert) zugegriffen wird.
Bei diesem Code
string[] x = [];
string s = "";
foreach (string xs in x)
s += xs;
bekomme ich aber keine Exception.
Wenn ich das richtig verstehe, was ich so gelesen habe, muss man hier mit einem Empty Enumerator arbeiten. Das Verstehe ich aber nicht richtig.
Wie muss ich meine enumeratoren verändern, damit ein foreach bei einer leeren Liste auch funktioniert?
Ich sehe gerade, bei dir sind die Bilder als byte[] drin. Wie hast du das denn gemacht?
Ich kann auch einen Typ auswählen. Aber nur Image, kein Bitmap.
Unter Speichern als habe ich auch noch zwei Möglichkeiten. Beide funktionieren aber nicht.
Wenn ich nun ein png hinzufüge (wie im Bild), kommt keine Fehlermeldung aber die png Datei taucht nicht in meiner Liste der Resourcen auf.
Also bei mir sieht das Fenster nun so aus
Gestern habe ich die neueste Visual Studio Version (17.11) installiert. Dort ist ein neues Resource Management integriert. Wenn ich nun Bitmap als Resource hinzufügen möchte, geht das nicht mehr. Vorher habe ich einfach Resource aus Datei hinzufügen angeklickt und dann die bmp oder png Datei ausgewählt.
Wenn ich nun aber Resource Hinzufügen anklicke, kommt ein Fensterm bei dem es nur den Typ Image gibt. Ich kann dann auch meine png Datei auswählen und es kommt auch keine Fehlermeldung. Allerdings wird mir da Bild in der neuen Resource Übersicht nicht angezeigt.
Meine bestehenden Resourcen haben auch als Typ (als Wert in der Typ Spalte der Übersicht) Bitmap. Beim Hinzufügen gibt es aber nur den Typ Image.
Wie bekomme ich nun mein Bitmap als Resource in mein Programm?
Wenn ich das richtig sehe, benutzen sowohl der PeriodicTimer als auch Task.Delay einen eigenen Task für die Ausführung. Ich habe die gesamte Poller-Funktion ein einem Task. Für alle Poll-Vorgänge (bei 5mSek Interval und 200mSek Timeout sind das schon 80 polls) benötige ich somit nur einen Thread/Task. Der Main Thread wird auch nicht ausgebremst, da ja alles in einem extra Thread/Task ist. Und beim Pollen benötige ich die GUI ja nicht.
Also für mich spricht nichts gegen meine Lösung abe einiges gegen PeriodicTimer und Task.Delay.
So etwas funktioniert und ist doch auch gar nicht so schlecht, oder?
private void button1_Click(object sender, EventArgs e)
{
trace.WriteLine("Start button1_Click");
asyncFunktion("Teil1: ");
trace.WriteLine("Ende button1_Click");
}
private async void asyncFunktion(string text)
{
trace.WriteLine("start von taskFunktion mit " + text);
int res = await Task.Run(() => pollerFunction(text));
trace.WriteLine("ende von taskFunktion mit " + text + ": res="+res);
}
private int pollerFunction(string text)
{
trace.WriteLine("start von pollerFunction mit " + text);
int iPoll = 0;
while (true)
{
iPoll++;
trace.WriteLine(iPoll + ". Poll von " + text);
if (iPoll >= 10) return 1;
Thread.Sleep(100);
}
}
Ich bin ja wohl willens, async/await zu benutzen. Mir fehlt da aber wohl noch etwas an Verständnis.
Bei HTTP Anwendungen habe ich es auch schon benutzt und als sehr angenehm erfahren. Aber da habe ich ja schon immer eine asynchrone Funktion, die ich einfach nur mit await aufrufen muss.
Jetzt habe ich aber eine fertige Methode.
Wie würde denn ein "normaler Poller" (das brauche ich ja erst einmal) mit async/await aussehen? Entweder denke ich zu kompliziert, oder... Ich habe auch noch kein vernünftiges Beispiel für so etwas gefunden. Praktisch brauche ich ja so etwas:
while (maximaleZeitNochNichtUm)
{
int result = myPoll(); //Hier wird ganz normal mit synchronem Code etwas abgefragt
if (result == 0)
{
//Ich bin fertig
return result;
}
Sleep(20)
}
//Timeout
return -2;
In einer einfachen Test-Applikation funktioniert es. Da ist der Report immer in ThreadID 1
namespace WinFormsApp1
{
public partial class Form1 : Form
{
TPcTraceSet trace = PcTraceSetAccessor.Neu("Test", true);
BackgroundWorker worker = new();
public Form1()
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void Worker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
trace.WriteLine("Worker_ProgressChanged");
}
private void Worker_DoWork(object? sender, DoWorkEventArgs e)
{
trace.WriteLine("In Worker Task");
while (true)
{
Thread.Sleep(1000);
worker.ReportProgress(1);
}
}
}
}
Ein einfacher Test Task in meinem Programm
public class TestBackground : BackgroundWorker
{
public readonly TPcTraceSet trace;
//**************************************************************************************************
public TestBackground()
{
trace = PcTraceSetAccessor.Neu("TestBackground", true);
trace.MitThreadID = true;
trace.WriteLine("TestBackground Konstruktor");
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
RunWorkerAsync();
}
//***********************************************************************************************************
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
base.OnProgressChanged(e);
trace.WriteLine("TestBackground.OnProgressChanged");
}
//***********************************************************************************************************
protected override void OnDoWork(DoWorkEventArgs e)
{
trace.WriteLine("In Worker Task");
while (true)
{
Thread.Sleep(1000);
trace.WriteLine("Vor ReportProgress");
ReportProgress(1);
}
}
}
}
funktioniert aber nicht. Da sehe ich sogar, dass OnProgress während des Laufens auf einmal in einer anderen ThreadId ausgeführt wird.
Mein Programm ist etwas komplexer. Keine Ahnung, wie ich das wohl geschaft habe, dass das Framework nicht mehr richtig funktioniert. Nun muss ich mir vielleicht doch was anderes überlegen. Habe es vor Jahren schon mal mit eigenen Nachrichten an mein Fenster gemacht. Das war aber noch unter .Net Framework und nicht unter .Net Core.
Oder ich muss doch für jede Ausführung einen eigenen Thread erzeugen. Vielleicht ist das Thread Erzeugen ja doch nicht so teuer.
Oder als async machen. Da bin ich aber noch nicht so ganz sattelfest. Wäre aber auch ein Task/Thread.
Hier was von meinem Code:
public class DataTransactionWorker : BackgroundWorker
{
//**************************************************************************************************
public DataTransactionWorker()
{
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
RunWorkerAsync();
}
//***********************************************************************************************************
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
base.OnProgressChanged(e);
if (actTransaction == null) ProblemTrace.WriteLine("DataTransactionWorker.OnProgressChanged with actTransaction == null");
else
{
DebugTrace.WriteLine("DataTransactionWorker.OnProgressChanged with " + actTransaction.TraceName + " transaction");
actTransaction.onReady(transactionError, actTransaction);
}
actTransaction = null; //Hauptsächlich, damit das Objekt vom Garbage Collector freigegeben wird
//Der Thread wartet im Semaphore, bis OnProgressChanged abgearbeitet wurde
//Von daher hier die Semaphore frei geben
reportSemaphore.Release();
}
//***********************************************************************************************************
protected override void OnDoWork(DoWorkEventArgs e)
{
while (CancellationPending == false)
{
actTransaction = pendingTransactions[0];
DebugTrace.WriteLine("DataTransactionWorker takes transaction " + actTransaction.TraceName);
try
{
if (actTransaction.Send() == false) transactionError = DataTransaction.eError.SendFailed;
else actTransaction.Poll()
}
catch (Exception ex)
{
ProblemTrace.WriteLine("Exception " + ex.Message + " on poll of " + actTransaction.TraceName);
transactionError = DataTransaction.eError.Exception;
}
ReportProgress(1);
reportSemaphore.Wait();
}
}
public partial class MainForm : Form
{
readonly DataTransactionWorker dataTransactionWorker = new();
}
Die Trace, die ich benutze, geben auch die Threadid aus. Also sehe ich, in welchem Thread was läuft
Hallo,
ich stelle gerade was ganz komisches fest: Ich habe eine BackgroundWorker, um Sachen in einem extra Thread zu machen. GUI updates geschehen in in der OnProgressChanged Funktion. Die sollte ja im Main Thread aufgerufen werden. Ich habe das auch schon oft so gemacht.
Nun bekomme ich aber die Fehlermeldung, dass das GUI Update aus dem falschen Thread heraus angetriggert wurde.
Wenn ich mir die ThreadId mit Environment.CurrentManagedThreadId anschaue, sehe ich auch, dass es drei Threads gibt:
Wie schafft man denn so etwas? Die BackgroundWorker Instanz wird auch im Main Thread erzeugt (habe ich kontrolliert). Die automatische Ausführung im GUI Thread ist der Hauptgrund, warum ich überhaupt einen BackgroundWorker benutze.
Im Moment steht deswegen meine Entwicklung und ich habe keine Ahnung, was ich da machen soll.
War in Urlaub und habe deswegen nicht geantwortet.
<Target Name="RenameOutputExecutable" AfterTargets="Publish" Condition="'$(Configuration)'=='InternRel'">
<Move SourceFiles="$(PublishDir)$(AssemblyName).exe" DestinationFiles="$(PublishDir)$(AssemblyName)Intern.exe"/>
</Target>
in die Projektdatei einbauen funktioniert super.
Vielen Dank.
Ich habe eine Windows Applikation, bei der ich eine interne Version und eine externe Version erstelle. Praktisch wird bei der internen Version keine Lizenz überprüft. Im Visual Studio habe ich dafür zwei Konfigurationen erstellt. Eine definiert NO_LIC_CHECK, die andere halt nicht. Somit kann ich im Code darauf reagieren.
Nun hätte ich gerne auch zwei unterschiedlichen Exe-Dateien. Also einmal app.exe und einmal appIntern.exe.
Unglücklicherweise lässt die Projekt Datei keine Condition für den Assembly Namen zu, wie z.B.
<PropertyGroup Condition="'$(Configuration)'=='Intern'">
<AssemblyName>appIntern</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Extern'">
<AssemblyName>app</AssemblyName>
</PropertyGroup>
Ich bekomme ja wohl unterschiedliche Verzeichenisse unter bin. Aber es steht halt der gleiche Name drin.
Nun habe ich mit zwei Veröffentlichungsprofile erstellt. Damit kann ich schon mal die interne und externe Konfiguration separat veröffentlichen. Damit kann ich auch zwei Zielverzeichnisse angeben. Somit bekomme ich schon unterschiedliche exe. Aber es ist halt eine Datei mit dem gleichen Namen in zwei Verzeichnissen.
Kann man das doch hinbekommen, dass ich unterschiedliche Dateinamen bekomme?
Es würde mich schon reizen, mal in die Implementierung etwas genauer hineinzuschauen. Prinzipiell muss doch "nur" der Renderer ausgetauscht werden. Da werden ja vielleicht nur Funktionen benutzt, die ein normales Grafics Ojekt des .Net auch kann.
Wie ist denn das Verhältnis vom SkiaSharp und .Net? Zumindest gibt es die SkiaSharp Doku Seiten doch unter .Net. Und wenn ich das richtig sehe, benutzt ScottPlot SkiaSharp. Und SkiaSharp benutzt das OpenTK, mit dem es die Probleme gibt. Gibt es dann vielleicht ein SkiaSharp unter .Net 8 ohne Warnungen? Vielleicht ist die Performance dann in einigen Bereichen nicht ganz so gut, aber damit könnte ich sehr wahrscheinlich leben.
Da ja (zumindest fast) alles open source ist, kann man ja gut in die Implementierung schauen. Ich habe gerade schon versucht, das Ergebnis in ein Bitmap rendern zu lassen, welches ich dann einfach anzeigen kann. Das läuft dann aber alles über SkiaSharp, wo ich den letzten Schritt nicht hinbekommen habe. In dem ScottPlot Paket ohne WinForm ist kein SkControl enthalten, was das wohl alles macht.
Da hat keiner von euch Erfahrung oder einen Hinweis?
Ich habe gerade die ScottPlot Komponente zum Zeichnen von Diagrammen gefunden und finde sie sehr gut. Allerdings gibt es eine unschöne Sache. Damit ich es in meiner WinForm Applikation "gut" verwenden kann, benutze ich das ScottPlot.WinForm NuGet Paket. Damit bekomme ich ein FormsPlot Objekt, mit dem ich arbeiten kann. Beim Compilieren bekomme ich allerdings die Warnung
Das Paket "OpenTK 3.1.0" wurde nicht mit dem Projektzielframework "net8.0-windows7.0", sondern mit ".NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1" wiederhergestellt. Dieses Paket ist mit Ihrem Projekt möglicherweise nicht vollständig kompatibel.
Zumindest im Moment ist das halt nur eine Warnung und ich habe noch nichts gefunden, was nicht funktioniert. OpenTK wird von der SkiaSharp Komponente bzw. deren WindowsForms Version benötigt.
Bei NuGet gibt es auch die ScottPlot Komponente (also ohne .WinForms), die auch viel öfters benutzt wird. Dort gibt es wohl nicht die .NetFramework Einschränkung. Allerdings scheint man damit auch keine Diagramme in einem Form zeichnen zu können.
Gibt es da vielleicht einen Trick oder habe ich nur das richtige noch nicht gefunden?
Ich hatte gerade so was ähnliches mit UDP und einer ICMP Antwort. Die Lösung war für mich .Net 8 zu benutzten (habe vorher noch immer .Net Framework benutzt) und dort die asynchronen Funktionen mit Cancel Token. Das hat zumindest bei mir super funktioniert. Mit dem Cancel Token lässt sich die blockierende Funktion super beenden.
An die asynchrone Programmierung muss man sich aber erst einmal gewöhnen. Sonst ist das für so etwas aber sehr gut geeignet und "funktioniert einfach".
Das sollte ich doch wohl mal ausprobieren. Da mein Designer aber Teil des NuGet Pakets ist (der Benutzer meiner Komponente soll sie ja im Visual Studio Designer auch komfortabel einstellen können) muss doch jeder Benutzer meiner Komponente dann auch das geänderte Windows SDK benutzen. Obwohl es ja vielleicht doch kein größeres SDK ist.
Als Alternative habe ich mir sonst schon gedacht, meinen Komponenten Designer als extra Programm zu machen. Die Komponente bekommt dann eine ConfigString Eigenschaft vom Typ string. Dieser String wird mir ja im Eigenschafts-Browser angezeigt. Über die Zwischenablage kann ich diesen manuell in mein Designer Programm übertragen. Und auch anders herum. Der String wäre dann zwar etwas länger, aber das sollte ja egal sein. Ich muss ihn ja nicht lesen können. Intern speichere ich die Konfiguration sowieso schon als Konfigurationsstrings ab. Der Aufwand sollte also nicht zu groß sein.
Das wäre halt nur "unhandlicher" für den Benutzer.
Aber wie geschrieben: ich muss es mal ausprobieren. Das wird aber sicherlich ein paar Tage dauern.
Dieser Forenbereich scheint nicht sehr begehrt zu sein. Vielleicht ist es auch "outdated". Aber es schein zu meinem Thema gut zu passen.
Ich bin gerade dabei, von .Net Framewort (also Version 4.8) auf .Net Core (also Version 8) umzusteigen. Dabei benutze ich den WinForm "Teil". Jetzt bin ich nur über ein Problem gestolpert, was sich eventuell als recht schwerwiegend herausstellt.
Ich habe eine eigene Komponente, die auch Design Editoren enthält. Im Eigenschafts-Browser erscheint bei einer Eigenschaft also ein kleiner Knopf, über den ein von mir geschriebenes Fenster aufgeht. Dort kann ich die Eigenschaft nur komfortabel ändern.
Bei der Deklaration der Eigenschaft in meiner Komponente füge ich das Attribute
Editor(typeof(XListBoxFarbenEditor), typeof(UITypeEditor)),
hinzu. Damit wird der Editor der Eigenschaft zugewiesen.
Das Ganze funktioniert im .Net Core aber nicht mehr. Über die Gründe habe ich einiges gelesen, z.B. https://devblogs.microsoft.com/dotnet/state-of-the-windows-forms-designer-for-net-applications/. Die Gründe sind auch einleuchtend.
Verstehe ich das denn richtig, dass es dafür noch eine richtige Alternative gibt? Gemäß https://github.com/KlausLoeffelmann/NetControlDesigners/tree/main kann man anscheinend was implementieren, was auf einen Klick im Designer Fenster reagiert. Das ist aber nicht im Eigenschafts-Browser.
Hat jemand Ahnung davon, ob es so etwas gibt? Oder muss ich damit leben, dass ich mit .Net Core keine eigenen Editoren für den Eigenschafts-Browser erstellen kann?
Ich habe ganz vergessen, hier zu antworten. Der Umstieg von .Net Framework auf .Net (Core) hat die Lösung gebracht. Wie von dannoe vorgeschlagen, benutze ich nun asynchrone aufrufe, denen ich ein Cancel Token mitgebe. Dann lässt sich das wunderbar per Cancel beenden.
In .Net 8 bekomme ich auch eine Exception beim Read, wenn der UDP Port die unreachable ICMP Message bekommt. In .Net 4.8 blieb er einfach in der read Funktion. Ich schätze, intern wurde dort aber auch so der Socket geschlossen, so dass ich nicht mehr senden konnte.
Man scheint so einen Beitrag ja nicht auf gelöst setzen zu können. Sonst würde ich das mit der Antwort von dannoe machen.
Ich habe noch nicht so viel mit dem asynchronen gemacht. Für einen dauerhaften Empfang frage ich mich aber, wie das funktionieren soll.
Ich brauche ja praktisch eine Endlosschleife, in der dauernd auf eingehende Pakete gewartet wird. Also praktisch
while (true)
{
data = udp.Receive()
handleData(data)
}
Wenn ich nur ReceiveAsync aufrufe, habe ich dann das folgende:
Der ganze while Loop ist in einem Task, eventuell sogar mein GUI/main task. Dann läuft der Code bis data=udp.ReceiveAsync(). Diese Funktion kommt dann doch gleich wieder zurück. Wenn ReceiveAsync später was empfangen hat, wird "der restliche Code" später im Context des Tasks, in dem ReceiveAsync aufgerufen wurde, ausgeführt. Dafür wird dann doch auch ein Hilfstask erzeugt, oder?
Da ich aus ReceiveAsync aber direkt zurück kommen, wird dann die while Schleife nicht weiter bearbeitet? Dann würde ich ja direkt wieder ein data=udp.ReceiveAsync aufrufen. Somit hätte ich dann zwei ReceiveAsync aktiv, was ich nicht möchte. Und nach ein paar Millisekunden ergibt das Hunderte.
Oder es funktioniert doch anders. Es wird die gesamte Ausführung der Funktion "pausiert". Die gesamte while Schleife ist also "der restliche Code", der erst ausgeführt wird, wenn Receive etwas empfangen hat. Also wird der nächste Schleifendurchlauf auch erst nach einen Paket Empfang durchlaufen.
Ist das so? Würde für mich Sinn machen.
Im aktuellen .Net (nicht der Version 4.8, die ich bisher noch benutze) gibt es ja auch eine ReceiveAsync Version, der ich ein Cancel Token übergeben kann. Das kann ich dann benutzen, wenn ich den ganzen UDP Client beenden möchte. Mit dem Cancel Token sollte ich das Receive ja beenden können. Dann kann ich auch die while Schleife beenden.
Ich müsst das ganze mal ausprobieren.
Mit meiner Beobachtung, dass das Receive aber gar nicht mehr funktioniert, wenn ich ein destination unreachable ICMP Paket bekommen habe, hat das aber alles nichts zu tun. Dieses Verhalten könnte sich aber noch als show stopper herauskristalliesieren.
Ich habe gerade folgende Beobachtung gemacht: per UdpClient schicke ich was weg und das Ziel ist nicht da. Mit Wireshark sehe ich, das es eine ICMP port unreachable Antwort gibt. Ok. Aber schicke ich danach was zu mit selber, kommt das bei mir nicht an.
Jetzt der Hintergrund. Als erstes, das Programm läuft mit WinForm und .Net Framework 4.8. Dann habe ich einen Client und einen Server, die über UDP kommunizieren sollen. Sowohl Client als auch Server laufen auf dem gleichen Rechner. Deswegen benutze ich zum Senden der UDP Daten die IP Adresse 127.0.0.1 (loopback). Dann habe ich einen Thread, in dem ich udpClient.Receive(...) aufrufe. Die Receive Funktion ist eine blockierende Funktion.
Wenn ich nun mit dem gleichen udpClient Objekt, bei dem im Thread die Receive Funktion aufgerufen wurde, Daten an mich selber schicke (also 127.0.0.1 als Ziel Adresse und die Portnummer, die udpClient benutzt, als Zielport) funktioniert das. Im Thread komme ich aus dem Receive heraus und sehe die empfangene Message. Wenn ich vorher aber was an einen anderen Port geschickt habe, und eine ICMP unreachable Message bekommen habe, komme ich aus der Receive Funktion nicht mehr heraus, wenn ich was an mich schicke.
Somit kann ich also nichts mehr an mich schicken. Wieso? Was kann man dagegen machen?
Das "Schicken an mich selber" brauche ich hauptsächlich, wenn ich den udpClient schließen möchte. Ich kann in meinem Hauptthread ruhig ein Close aufrufen. Deswegen komme ich im Thread aber nicht aus der Receive Funktion heraus. Somit hängt der Thread noch und ich kann ihn nicht beenden. Wenn ich aber eine "Schließen Message" an mich selber schicke, kann ich das im Thread erkennen und diesen beenden. Das funktioniert sehr gut. Aber nur so lange, bis ich eine unreachable Message bekommen habe.
Gibt es eine andere Möglichkeit, innerhalb des Threads zu erkennen, dass der Socket geschlossen wurde und dann den Thread zu beenden?
Unglücklicherweise steht hinter den Links keine Lösung. Es steht dort nur, dass es mit aktuellem Android nur über scoped storage geht. Wie das geht, steht dort aber nicht.
Ich hatte das Problem schon mal, als ich ein Programm mit dem Android Studio (also direkt unter Android) geschrieben haben. Dort habe ich auch wochenlang rumprobiert und (auch die genannten) Dokumente / Web Seiten mehrfach gelesen. Mit einem File Picker kann man wohl eine Datei auswählen, für die man dann einen URL Link erhält, den man mit anderen Mapper Komponenten auch öffnen konnte. Aber ein Verzeichnis öffnen und dann auslesen, welche Dateien es dort gibt und einige dann sogar öffnen, habe ich nicht hinbekommen. Ich bin mir auch nicht sicher, ob das überhaupt geht. Allerdings wäre das ja für mich eine Grundfunktionalität eines Android Betriebssystems. Oder man muss sehr aufwendig viele URLs finden und mit irgendwelche Mappern arbeiten.
Ich habe mich damals dazu entschieden, meine Daten im App-Spezifischen Bereich zu speichern und eine IP-Basierte eigene Zugangsfunktion zu implementieren. Das hat insgesamt weniger Zeit gebraucht als ich vorher hineingesteckt habe, um einen "vernünftigen Dateizugriff" zu implementieren.
Die eigene IP-Basierte Zugangsfunktion passte damals recht gut ins Konzept. Jetzt nicht. Ich hatte gehofft, mit dem .Net MAUI gäbe es eine Möglichkeit, auf die "normalen Dokumente" des Benutzers auch in Android zugreifen zu können. Aber die "super" Android Entwickler haben das ja wohl "so toll" implementiert, dass es für "Normalprogrammierer" nicht zu benutzen ist.
Jetzt wird mir auch wohl nur dieser Weg übrig bleiben. Schade.
Oder hat jemand eine funktionierende Lösung?
Ich würde von meiner .Net MAUI Applikation gerne Dateien im Dokumente Ordner eines Android Handys speichern und lesen. Es muss nicht einmal der Dokumente Ordner einer SD Karte sein. Aber das funktioniert nicht.
Zuerst habe ich mein Handy per USB mit dem (Windows) PC verbunden und den Datenaustausch aktiviert. Somit habe ich also die Verzeichnisse auf des Handys auf dem PC gesehen und konnte auch eine Text-Datei (zu Testzwecken) auf das Handy kopieren.
Nun benutze ich den FolderPicker aus dem CommunityToolKit, um den Pfad auszuwählen:
var folderPickerResult = await FolderPicker.PickAsync(CancellationToken.None);
Der funktioniert einwandfrei. Es öffnet sich ein Fenster auf meinem Android Handy und ich kann das entsprechende Verzeichnis auswählen. Ich habe damit auch schon ein Verzeichnis erstellt, welches ich dann auf dem PC auch sehe. In dem Picker-Fenster wird mir auch meine Text-Datei angezeigt, die ich vom PC kopiert habe. Wähle ich im Picker nun das Verzeichnis aus, erfolgt auch die Abfrage, ob ich (weiterhin) Zugriff auf dieses Verzeichnis haben möchte. Das sieht also alles total richtig aus. Als Pfad wird mir
/storage/emulated/0/Documents/Verzeichnis
angezeigt, was ja auch völlig korrekt ist.
Nun möchte ich das Verzeichnis aber in meiner App benutzen, z.B.
DirectoryInfo directoryInfo = new(VerzeichnisName);
if (directoryInfo.Exists)
{
Debug.WriteLine("Verzeichnis "+VerzeichnisName+" gibt es: "+directoryInfo.CreationTime);
FileInfo[] fileInfos = directoryInfo.GetFiles("*.*");
Debug.WriteLine(fileInfos.Length + " Dateien gefunden");
}
DirectoryInfo sagt mir, dass es das Verzeichnis gibt und die CreationTime passt auch. Mit GetFiles bekomme ich aber immer "0 Dateien gefunden". Auch die GetFiles() Version ohne Parameter liefert mir keine Datei.
Im Android Manifest meiner App habe ich folgende Permission "angehakt"
- WRITE_EXTERNAL_STORAGE
- READ_MEDIA_AUDIO
- READ_MEDIA_IMAGES
- READ_MEDIA_VIDEO
- READ_EXTERNAL_STORAGE
- MANAGE_EXTERNAL_STORAGE
- MANAGE_DOCUMENTS
Obwohl ich ja nicht mal auf den externen Speicher zugreife, sollte das doch wirklich ausreichen.
Wenn ich die Permissions abfrage, bekomme ich auch immer Denied. Aus ein Request funktioniert nicht und gibt Denied zurück
public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new List<(string androidPermission, bool isRuntime)>
{
(global::Android.Manifest.Permission.ReadExternalStorage, true),
(global::Android.Manifest.Permission.WriteExternalStorage, true)
}.ToArray();
}
PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
Debug.WriteLine("Check StorageRead status = " + status);
status = await Permissions.RequestAsync<Permissions.StorageRead>();
Debug.WriteLine("Request StorageRead status = " + status);
status = await Permissions.CheckStatusAsync<ReadWriteStoragePerms>();
Debug.WriteLine("Check ReadWriteStoragePerms status = " + status);
status = await Permissions.RequestAsync<ReadWriteStoragePerms>();
Debug.WriteLine("Request ReadWriteStoragePerms status = " + status);
Allerding bekomme ich von DirectoryInfo ja auch keine Exception "No access". Das kam meine ich bei meinen ersten Tests.
Benutze ich den FilePicker (ja direkt in MAUI drin) kann ich meine Test-Datei auch direkt auswählen. Dort bekomme ich aber
/storage/emulated/0/Android/data/com.companyname.myapp/cache/2203693cc04e0be7f4f024d5f9499e13/c853c2cc6be445ae84107449a540c65f/test.txt
als Pfad. Das ist ja wohl die Cache oder Sandbox Version der Datei. Damit kann ich aber auch nicht arbeiten.
Ich stehe nun ich glaube ich zum dritten Mal vor diesem Problem, was ich bisher noch nicht gelöst bekommen habe. Der bisherige Workaround war dann immer, die Daten in das App-spezifische Verzeichnis zu speichern und mir eine spezielle Export Funktionalität, z.B. über WLAN zu implementiern, um die Daten aus dem Handy zu bekommen. Es wäre aber schön, wenn das mal funktionieren würde und ich die Daten in ein Verzeichnis speichern kann, auf das ich vom PC aus auch zugreifen kann.
Eventuell ist so etwas unter Android aber auch gar nicht möglich.
Sehe ich das eigentlich richtig, dass man mit .Net MAUI nicht auf eine Mouse Down Ereignis reagieren kann?
Wenn ich das richtig sehe, gibt es dort ja "nur" Gesture Recogniser:
Für mehr als 90% der Fälle ist diese Aufteilung ausreichend und generell macht sie auch Sinn. Allerdings möchte ich gerne schon was machen, wenn die Maus nur gedrückt wird. Wenn sie dann ohne Bewegung losgelassen wird, kann das Tab Ergeignis ja ruhig kommen. Und wenn sie bewegt wird, ist das Pan Ereignis auch gut (die Startposition nehme ich mir im Moment halt aus dem PointerGestureRecognizer, der aber dafür immer mitlaufen muss). Aber der spezielle Fall, Maus gedrückt, noch nicht bewegt und auch noch nicht losgelassen, kann nicht abgebildet bzw. erkannt werden.
Ich habe schon einen Hinweis gelesen, dass man so etwas in dem plattformspezifischen Teil selber implementieren kann. Das bräuchte ich dann aber sowohl für Windows und Android. Im Moment habe ich wenig Ahnung, wie so etwas dann wohl implementiert wird.
Die Seite hatte ich vorher auch schon gelesen. Es macht aber keinen Unterschied, ob ich einen Haken bei SEHException mache oder nicht.
Die Settings werden ja auch im popup Fenster der Exception angezeigt. Wenn ich das richtig verstehe, hält der Debugger an, bevor der Exception Handler aufgerufen wird. Also wenn das Häkchen gesetzt ist. Aber wird das nicht sowieso immer schon gemacht? Als Standard sind ja auch die meisten Häkchen nicht gesetzt.
Ich hatte dann auch mal ausprobiert, dass ich zu dem Häkchen noch eine Bedingung gesetzt habe. Aber das macht auch kein Unterschied. In allen Fällen bekomme ich das angehangen Bild.
Ich habe ein merkwürdiges und sehr lästiges Problem, wo ich nicht weiter weiß.
ich habe eine .Net 7.0 Windows Forms Anwendung, die für Windows mit der Zielversion 10.0.19041.0 gebaut wird. Dort spreche ich mit einen HID Device. Das "sprechen" geht in folgenden Schritten:
Die entsprechenden HID Funktionen sind als DllImport eingefügt. Das ist dann also nativer Code und kein verwalteter.
Lasse ich das nun im Visual Studio Debugger (Debug Konfiguration mit x64) laufen, bleibt er mit einer System.Runtime.InteropServices.SEHException: "External component has thrown an exception." in LibraryImports.g.cs hängen. Die Aufrufliste ist:
Visual Studio zeigt mir auch an, dass es im GC-Finalizer-Thread passiert.
Ich schließe mein SafeHandle aber gar nicht.
Merkwürdiger ist auch noch, dass das Programm problemlos durchläuft, wenn ich es nicht im Visual Studio (Debugger) laufen lasse, sondern die Exe unter dem bin Verzeichnis starte. Wenn dort die Exception auftreten würde, würde mein Programm doch beendet werden. Und wenn die Exception irgendwo abgefangen würde, würde das doch auch im Visual Studio passieren. Dann könnte ich dort das Programm ja weiter laufen lassen.
Oder hat das was mit GCHandle zu tun? Die nativen Funktionen erwarten einen fixen Datenpointer. Den habe als GCHandle mit
handle = GCHandle.Alloc(ByteArray, GCHandleType.Pinned)
auf mein normales ByteArray erzeugt.
Aber ich lösche doch weder mein Datenhandle (was vielleicht intern auch ein SafeFileHandle benutzt) noch mein Dateihandle.
Wieso kommt dann die Exception? Und dann nur beim Debuggen?
Vielleicht wurde das schon mehrfach diskutiert, ich habe aber nichts passendes gefunden.
Ich würde gerne ein Google Abfrage machen. Erst dachte ich, das ist ja ganz einfach. Ein HttpResponse auf "https://www.google.de/search?q="+suchwort machen. Auf diese Anfrage bekomme ich aber eine HTML Antwort, dass ich mich anmelden soll oder cookies akzeptieren soll.
Dann habe ich mehr gegoogelt und eine custom search engine erstellt, über die man (das ist dann ja wohl die google search api) eine Anfrage mit einem Schlüssel stellen kann. Dort bekomme ich dann auch Suchergebnisse. Diese Ergebnisse sind aber total unterschiedlich zu denen, die ich bei einer Eingabe in meinem Browser bekommen. Das sie leicht unterschiedlich sind, kann ich ja noch verstehen. Aber ich würde sagen, sie sind total unterschiedlich.
Kann man nicht doch den Weg über die "normale" Adresse gehen? In der HTML Antwort, die ich ja auf die einfache (oben angegebene) Adresse bekomme, gibt es einen customButtonContainer mit einem "continue Link". Wenn ich den aufrufe, kommt aber nichts vernünftiges zurück.
Kann ich mit einem (einfachen) C# Programm doch nicht so tun, als wäre ich ein Browser und den HTML Inhalt bekommen, den ich auch im Browser sehen würde?
Das müsste auch "ganz normal zu Fuß" gehen. Zwischen MouseDown und MouseUp bekommt man auch MouseMove Events, wenn man sich außerhalb des Controls befindet. Wenn man also beim MouseDown "nahe genug" am Rand ist, und ein Resize auch möchte, kann man bis zum MouseUp die Mausbewegungen mitbekommen und die neuen Größen ausrechnen.
Eventuell musst du aber noch die Maus Koordinaten auf Screen Koordinaten umrechnen. Wenn z.B. die Maus 3 Punkte nach rechts bewegt wurde und du deswegen Width um 3 erhöht hast, bekommst du eventuell noch ein MouseMove Event mit einer Position, die 3 Punkte links davon ist. Nach einem Umrechnen auf Screen Koordinaten (Control.PointToScreen) ergibt sich aber die gleiche Koordinate.
Zuerst einmal ein Wort zu dem letzten Kommentar:
Sorry, in meiner 30 jährigen Arbeit als professioneller Software Entwickler hatte ich bisher nicht so viel mit den Details der verschiedenen .Net Versionen zu tun. Von daher sehe ich nicht nach 3 Sekunden an einer längeren Liste von vielen Begriffen direkt was das alles bedeutet. Wenn ich alles wüsste, bräuchte ich ja nicht im Forum zu fragen.
Aber da will ich nun nicht drauf rumreiten, obwohl mich diese Antwort (vor allem das zwischen den Zeilen) doch sehr verärgert hat.
Mein Problem ist aber gelöst.
Wie ich ja geschrieben hatte, liegt BLE unter dem Windows Devices Namespace. Der steht aber nur zur Verfügung, wenn man unter "Version des Zielbetriebssystems" eine 10.xy auswählt. Gibt man dort 7.0 an, gibt es Devices nicht mehr. Das macht sogar (auch für mein nicht vollkommenes Wissen, was ich mir aber so zusammengesucht habe) Sinn. Das Net Core ist ja für verschiedene Plattformen gedacht. Selbst wenn ich nun also als Zielbetriebssystem Windows auswähle, dann aber als Version 7.0 auswähle, sage ich praktisch: Nimm nur das, was es auch in anderen Plattformen gibt. Damit fällt Devices raus. Es steht ja auch im Internet, dass Devices aus den höheren .Net Versionen herausgenommen wurde. Nun dachte ich aber, das gilt für das gesamte Framework. Wenn man also in seinem Projekt .Net 7.0 als Zielframework angibt, würde das nicht zur Verfügung stehen. Das ist aber wohl falsch.
Mit der richtigen Version des Zielbetriebssystems kann ich nun auch Code aus den UWP (UWP ist ja per Definition immer nur für Windows) Beispielen benutzen.
Ich habe mir alles heruntergeladen und einiges geschaut und ausprobiert. Ich konzentriere mich jetzt mal auf InTheHand.BluetoothLE.
Das ganze scheint für das .Net Framework zu sein und nicht for .Net Core. Neben der Fehlermeldung, dass ich nicht den richtigen Android API level installiert habe, sagt er, dass .NETFramework V4.6.1 eingestellt ist, was bei mir nicht installiert ist. Ich habe im Projektfile dann alle Zielversionen außer Windows entfernt. Ich habe also nur net7.0-windows10.0.19041.0 übrig gelassen. Damit compiliert es dann.
Das Beispiel (ConsoleBLETest) hat als Zielframework .NET Core 3.1 angegeben. Da sagt er dann, dass man von einer .NETCoreApp kein net7.0... Projekt referenzieren kann. Bei dem DLL Project (also das BluetoothLE Projekt) kann man kein .NET Core als Platform eingeben. Auf der GUI akzeptiert er es nicht. Wenn ich im Projektfile TargetFrameworks auf netcoreapp3.1 setze (das steht bei der Console App), kommen 55 Fehler im DLL Projekt, dass die Namen im aktuellen Kontext nicht vorhanden sind.
Wenn ich das BLE DLL Projekt mit dem
Im Bluetooth DLL Projekt gibt es auch Win32 und Windows als Platforms. Im BLE Projekt nur Windows. Dort gibt es noch eine Wasm Platform. Die scheint aber was mit Java zu sein. So scheint es für mich, dass es bei BLE keine Win32 Implementierung gibt (was ich ja immer wieder feststelle). Unter Windows Platform wird auch ganz normal Windows.Devices verwendet, was es ja nur bei UWP gibt. Win32 scheint also wirklich kein BLE zu unterstützen. In den Win32 Platform sourcen des Bluetooth Projekts sind die normalen Bluetooth Strukturen der Win API benutzt. Das hilft halt nur bei BLE nicht.
Ich habe übrigens nicht herausgefunden, warum die Exception geworfen wird. Und hinter dem eingefügten Link habe ich auch nichts entsprechendes gefunden.
32feet scheint ja doch alles zu haben, was ich brauche. Das wäre ja klasse.
Vielleicht bin ich jetzt ja ganz blöd.
Ich wollte das Beispiel 32feet/Samples/ConsoleBLETest at main · inthehand/32feet (github.com) hier laufen lassen. Also habe ich ein Konsolen-App Projekt erstellt. Also unter Projektvorlagen den Eintrag "Konsolen-App Ein Projekt zum Erstellen einer Befehlszeilenanwendung, die mit .NET unter Windows, Linux und macOS ausgeführt werden kann" ausgewählt. Es gibt ja nur zwei Konsolen Vorlagen: diese und eine mit .NET Framework.
In dem Projekt habe ich dann das NuGet Paket InTheHand.BluetoothLE (4.0.33) installiert.
Unter Program.cs gibt es keine Main Funktion (finde ich ungewöhnlich, aber das haben die ja vielleicht so gemacht). Rufe ich dort
bool allowed = await Bluetooth.GetAvailabilityAsync();
auf, bekomme ich false. Bei dem Beispiel steht auch, dass man eventuell eine Bluetooth Permission angeben muss. Wie das unter Windows funktioniert, steht dort aber nicht. Mir ist auch nicht bewusst, dass man dort eine Berechtigung vergeben kann bzw. muss. Die BLE UWP Testprogramme funktioniert ja auch auf dem Rechner.
Zuerst hatte ich die GetAvailability Abfrage nicht drin. Und ich habe Probleme mit dem async, await und task. Das habe ich bisher noch nie benötigt und bin da nicht sattelfest. Bei ScanForDevicesAsync bekomme ich eine Exception "Operation is not supported on this platform". Also funktioniert das doch nicht bei Windows?
Hier mal da aktuelle Program.cs:
// See https://aka.ms/new-console-template for more information
using InTheHand.Bluetooth;
bool allowed = false;
async Task<bool> TestDeviceDiscovery()
{
Console.WriteLine("In DestDeviceDiscovery");
return true;
//var discoveredDevices = await Bluetooth.ScanForDevicesAsync();
//Console.WriteLine($"found {discoveredDevices?.Count} devices");
//return discoveredDevices?.Count > 0;
}
async Task<bool> TestAvailability()
{
allowed = await Bluetooth.GetAvailabilityAsync();
return allowed;
}
//bool allowed = await Bluetooth.GetAvailabilityAsync();
var availableTask = TestAvailability();
availableTask.Wait();
Console.WriteLine("allowed = " + allowed);
Console.WriteLine("Vor Funktionsaufruf");
var discoveredDevices = await Bluetooth.ScanForDevicesAsync();
Console.WriteLine("Nach Wait");
string line = Console.ReadLine();
Die Artikel hatte ich noch nicht gefunden. Sie haben mich doch recht optimistisch gestimmt. Dann kommt aber das große ABER.
Der Artikel ist schon etwas älter und benutzt nicht das aktuelle .Net. Im aktuellen .Net wurde das Einbinden von Windows.winmd aber untersagt (NETSDK1130: Direkter Verweis auf Windows Metadata-Komponente nicht möglich - .NET CLI | Microsoft Learn). Ich wurde dann auf microsoft/CsWinRT: C# language projection for the Windows Runtime (github.com) verwiesen. Gemäß meinem Verständnis kann man damit eine UWP Bibliothek erstellen, die man dann in eine .Net Bibliothek umwandelt. Mit Hilfe des NuGet Pakets.
Das funktioniert bei mir aber auch nicht. Ich bekomme immer den Fehler: Das Projekt <die UWP Library> ist nicht mit net7.0-windows10.0.22621 (.NETCoreApp, Version=7.0) kompatibel. Das Projekt unterstützt Folgendes: uap10.0.17763 (UAP, Version=v10.0.17763)
Anscheinend kann man doch keine Bibiliothek von UAP mit .NETCoreApp verbinden. In deren Beispiel CsWinRT/src/Samples/NetProjectionSample at master · microsoft/CsWinRT (github.com) haben die aber auch ein UAP Projekt, was nichts von Windows benutzt. Die haben einfach einen mathematischen Rechner, dier plus, minus, usw. macht. Und deren UAP Projekt ist in C++. Meine Library aber in C#. Ich dachte aber, das macht keinen Unterschied.
Das scheint wohl alles nicht zu funktionieren. Und die Windows.Devices Teile (sind ja wohl der low level Zugriff auf die HW), in denen das BLE drin ist, gibt es anscheinen wirklich nur für UWP. Ich habe auch nach Win32 API Funktionen für BLE gesucht. Die scheint es aber komischerweise nicht zu geben. Ich habe wohl Bluetooth API (den Link finde ich im Moment nicht) gefunden. Das ist aber was anderes als Bluetooth Low Energy.
Es wurde unten gesagt, ich könnte in Sourcen von Open Source Projekten nachschauen. Das war für NuGet Paket gemeint, oder? Es gibt aber doch nicht von allen NuGet Paketen den Source Code, oder? Da muss ich vielleicht noch mal wühlen.
Generell bin ich wohl nicht der Einzige, der über dieses Problem gestolpert ist. Man findet wohl Fragen, aber keine Antwort, wie das ohne UWO funktioniert. Das ist schon blöd. Vielleicht hat ja noch jemand einen Tip.
Ich möchte mit einem Windows Programm mit einem BLE Gerät kommunizieren.
Wenn ich das Internet richtig verstehe, gibt es das aber nur für Universal Windows Plattform (UWP). Hier für gibt es auch Beispiele, die auch bei mir funktionieren.
Ich würde aber lieber das "normale .Net", idealerweise mit Windows Forms (aber schon .Net Core und nicht .Net Framework), benutzen. Das funktioniert aber nicht? Selbst bei einer Suche nach einem Windows SDK hieß es, dass BLE nur bei UWP benutzt wird. Das kann doch gar nicht sein, oder? Es gibt wohl eine Bluetooth API im "normalen?" Windows SDK. Aber BLE ist doch was anderes, oder? Wenn ich per dll einige Windows Routinen einbinde, und die dann in meinem C# Programm benutze, wäre das auch ein Weg. Vielleicht nicht der einfachste, aber ...
Bei NuGet gibt es auch einige BLE Pakete. Aber da steht doch auch nie Windows dran. Oder interpretiere ich da was falsch?
Hat jemand schon mal BLE unter Windows gemacht?
Gruß
Erwin
Dann muss ich wohl noch mal lesen, wie das mit Rechten Anfordern ist.
Einen Tip?
Wir sind einen großen Schritt weiter.
Das mit dem Zertifikat installieren (wenn auch manuell) und das Installationspaket zu installieren funktioniert auch auf dem anderen Rechner.
Wenn das Programm dann das erste Mal mit Admin Rechten gestartet wird, funktioniert es auch. Danach funktioniert es auch ohne Admin Rechte.
Jetzt ist halt nur die Frage, wofür benötigt das Programm das erste Mal Admin Rechte? Ich benutze Preferences. Kann es sein, dass zum Erstellen der zugehörigen Datei Admin Rechte erforderlich sind? Zum schreiben/lesen später aber nicht mehr?
Ich müsste also mal eine Version ohne Preferences machen und diese dann auf einem frischen Rechner ausprobieren.
Ich habe es nun doch hinbekommen.
Beim Erstellen des Pakets in Visual Studio habe ich ein neues Zertifikat erzeugt und als vertrauensvoll eingestuft.
Danach habe ich dieses mit der Windows Zertikatsverwaltung in eine Datei exportiert. Die konnte ich dann auf dem anderen Rechner unter Local Machine und unter vertrauensvolle Person importieren. Damit lies sich dort dann auch das Programm installieren.
Danke für den Schups in diese Richtung.
Ich habe versucht, das Self CA auf einem anderen Rechner zu installieren. Das hat aber nicht funktioniert.
Genau genommen hat das Installieren schon funktioniert. Er hat es aber immer unter Local User installiert (zumindes bei einem normalen doppelklick auf die erzeugt Schlüsseldatei). Ich muss es aber beim anderen Rechner doch unter Local Machine installieren und dann auch noch als Vertrauenswürdig einstufen.
Wie mache ich das denn?
Vielleicht noch ein Punkt. Ich würde ja wohl ein MSIX Package benutzen. Aber dafür braucht man ja ein Code-Signing Zertifikat, welches ich nicht habe. Und diese Zertifikate sind ja ganz so günstig.
Es gibt ja wohl keine Möglichkeit, das als MSIX Package zu erzeugen ohne ein Zertifikat zu haben, oder?
Wie in einigen Beiträgen zu lesen ist, bekommt man ein "wirkliches Exe", indem man commandNmae in launchSettings.json auf Project ändert und in einer PropertyGroup der Projekt Datei WindowsPackageType = None einfügt. Das funktioniert bei mir auch. Ich kann auf dem einen meiner Rechner so eine Exe machen. Die kopiere ich dann (zusammen mit den anderen Megabyte im Verzeichnis) auf den anderen Rechner und kann sie dort ausführen.
Halt nur nicht auf dem Rechne meines Kollegen. Bei einem zweiten Kollegen funktioniert es übrigens auch nicht. Ich schätze mal, das funktioniert auf allen anderen Rechnern nicht. Vielleicht nur auf denen, bei denen auch Visual Studio für MAUI installiert ist.
Ich habe eine MAUI App geschrieben, die beim Builden eine lauffähige Exe erstellt. Die läuft auch sowohl auf meinem Desktop als auch auf meinem Laptop. Nun habe ich die Exe (zusammen mit den 40MB anderen Dateien, die noch dabei liegen) auf den Rechner eines Kollegen kopiert.
Dort startet das Programm soweit, dass das Fenster korrekt angezeigt wird. Man kann aber nichts machen. Gemäß Task Manager werden auch 0% CPU Zeit benötigt.
Zuerst war wohl nicht das .Net Core 7 auf dem Rechner installiert. Das haben wir aber nachgeholt. In der Konsole werden nun unter dotnet --list-sdks und dotnet --list-runtime die Versionen (sogar die 7.08, wobei ich die 7.07 habe) angezeigt. Das hat auf mein Programm aber keinen Einfluss.
Dann habe ich gelesen, dass bei einigen das Installieren der WinUI 3 Gallery geholfen hätte. Beim Kollegen aber nicht. Auch ein installieren (bzw. update) des Windows App SDKs hat keine Änderung gebracht.
Naiv wie ich bin hätte ich erwartet, dass das Programm erst gar nicht startet, wenn kein passendes .Not Core installiert ist. Besser noch, dass es eine Fehlermeldung gibt, die darauf hinweist. Aber hier kommt nichts.
Wie finde ich denn nun heraus, was meinem Programm auf dem anderen Rechner fehlt? Ich kann ja auch schlecht debuggen um festzustellen, was das Programm überhaupt macht. Vielleicht habe ich ja auch was falsch gemacht. Da bin ich aber skeptisch; es läuft ja auf meinen beiden Rechnern.
Allerdings ist auf meinen beiden Rechner auch Visual Studio mit der MAUI Entwicklung installiert.
Irgendwelche Tipps?
Mit DesiredSize und dem SizeChanged Event klappt das sehr gut.
Für mich überraschend war nur, dass ich LayoutOptions beide auf Center stellen musste, damit das Item auch in dem Rechteck liegt, was ich übergeben habe. Also die linke obere Ecke ist auch dort, wo ich x und y des Rect eingegeben habe. Ich hatte erwartet, dieses Verhalten habe ich bei LayoutOptions Start.
Aber wie gesagt, es klappt nun.
Aber es gibt ja auch noch DesiredSize.
Wo steht denn wohl der Zusammenhang zwischen DesiredSize, Requested Size und aktueller Size erklärt? Und wann wird die Größe dann gesetzt?
Das habe ich alles gelesen.
Wenn man aber die Größe der Child Elemente kennt, und auch die eigene (also des Layouts), ist das doch gar nicht so schwer. Zumindest war es das bisher bei meiner Implementierung. Da ich aber auch wohl die Größe meiner Child Elemente ändern kann (ich übergebe ja ein Rect) gibt es vielleicht ein Henne und Ei Problem. Ich muss die (benötigte) Größe kennen, um die (richtige) Größe berechnen zu können.
Ich werde mal weiter etwas herumspielen. Prinzipiell kann ich mich doch bei allen meinen Kindern auf SizeChanged Ereignisse registrieren und wenn ich dann die Größen aller meiner Kinder habe, kann ich sie ja positionieren.
Ich versuche die Positionierung von Elementen mit dem AbsoluteLayout zu verstehen.
Wo greift man am besten ein, um die untergeordneten Views (ich denke, dass sind ja alles Views) anzordnen?
Zum Testen habe ich eine Klasse von AbsoluteLayout vererbt, die als Content in meine ContentPage Klasse eingefügt wird.
Dann kann ich die OnSizeAllocated Funktion in meiner Layout Klasse überschreiben, in der ich mit den width und height Parametern schon mal den zur Verfügung stehenden Platz habe. In meinem Test habe ich erst einmal einfach BoxView Objekte erstellt. Bei denen habe ich die WidthRequest und HeightRequest Parameter gesetzt. Die kann ich in der OnSizeAllocated Funktion abfragen und die Boxen gewünscht positionieren. Die Width und Height Eigenschaften sind zu dieser Zeit noch nicht gesetzt.
Aber wenn ich mehr zusammengesetzte Strukturen in meiner Layout Klasse als Content habe, z.B. andere Layouts oder etwas in einer Border. Bei diesen Strukturen ist WidthRequest und HeightRequest nicht gesetzt. Ich habe also keine, zumindest gewünschte oder "normale", Größe meiner Child Objekte. Die benötige ich aber, um die Position der Childs zu setzen. Da das Setzen per Rect erfolgt, kann bzw. muss ich ja auch eine Breite und Höhe angeben, die mir ja fehlen.
Auch nach einigem Suchen im Netz habe ich nicht gefunden, wie das Layouten funktioniert. Es muss doch irgendwie so sein, dass erst einmal die Größe der Childs berechnet wird, die dann abgefragt werden kann, und womit die Childs dann angeordnet werden kann.
Kann mir da jemand helfen, das zu verstehen?
Was sind den Dev-Fehler?
Device Fehler? Developer Fehler?
Muss ich nun also damit leben?