Links| Links
Quellcode des .NET Frameworks Microsoft Reference Source .NET Framework
VS2022 Single File Applikation| VS2022 Single File Applikation
Damit das ewige Suchen ein Ende hat, hier ist es erklärt: Veröffentlichen einer Einzeldatei-App.
Zifferneingabe-TextBox| Zifferneingabe-TextBox
Um die Eingabe in ein Steuerelementen auf Ziffern zu beschränken, muss das KeyPress-Ereignis des Steuerelements ausgewertet werden:
Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
Select Case e.KeyChar
Case "0"c To "9"c, Chr(8), Chr(26), Chr(3), Chr(1) ' Ziffern, Backspace, Ctrl-Z, Ctrl-C, Ctrl-A
' Nichts zu tun, an die interne Verarbeitung weitergeben
Case Chr(22) ' Einfügen per Ctrl-V
Try
Dim Input As String
Input = My.Computer.Clipboard.GetText().Trim ' Versuchen 'Text' aus dem Clippboard zu lesen
Integer.Parse(Input) ' Repräsentiert der Text eine Ganzzahl?
CType(sender, TextBox).SelectedText = Input ' oder ToolStripTextBox bei TextBox im Contextmenu
Finally
e.Handled = True
End Try
Case Else ' Restliche Zeichen nicht verarbeiten
e.Handled = True
End Select
End Sub
Applikationseinstellungen benutzerdefiniert speichern| Applikationseinstellungen benutzerdefiniert speichern
Der Standard-Mechanismus den Visual Studio für das Speichern von benutzerspezifischen Anwendungseinstellungen anbietet, speichert diese in den Tiefen von "<user>/AppData". Dabei hinterlässt er ein ziemliches Chaos an Dateien, weil u.a. für jede Version ein eigenes Verzeichnis angelegt wird.
Mit folgendem einfachen Prinzip kann man hier ebenso komfortabel unter eigener Kontrolle durchführen.
Imports System.IO
Imports System.Xml.Serialization
''' <summary>
''' Muster-Klasse für das Speichern und Laden von benutzerdefinierten Anwendungseinstellungen
''' mit individueller Festlegung des Speicherorts.
''' </summary>
Public Class AnyAppSettings
''' <summary>
''' Dummy-Property, automatisch implementierte Eigenschaft.
''' </summary>
''' <value>Irgendeine Farbe.</value>
Public Property AnyColor As Color = Color.DarkRed 'Standardwert ist DarkRed
Private _AnyIndex As Integer = 0
''' <summary>
''' Dummy-Property, Get + Set
''' </summary>
''' <value>Irgendein Integer.</value>
Public Property AnyIndex As Integer
Get
Return _AnyIndex
End Get
Set(value As Integer)
If _AnyIndex < 0 Then
Throw New ArgumentOutOfRangeException("AnyIndex", "AnyIndex must not be negative.")
End If
_AnyIndex = value
End Set
End Property
''' <summary>
''' Speichert die öffentlichen Eigenschaften in die angegebene Datei.
''' </summary>
''' <param name="Path">Dateipfad.</param>
''' <remarks>Die Datei wird überschrieben.</remarks>
Public Sub Save(Path As String)
Dim Writer As New StreamWriter(Path, False)
Dim ser As XmlSerializer = New XmlSerializer(GetType(AnyAppSettings))
ser.Serialize(Writer, Me)
Writer.Close()
End Sub
''' <summary>
''' Liest die öffentlichen Eigenschaften aus der angegebenen Datei.
''' </summary>
''' <param name="Path">Dateipfad.</param>
''' <returns>Eine neue Instanz der <see cref="AnyAppSettings"/>-Klasse mit den einmgelesenen Werten.</returns>
''' <remarks>Schlägt das Lesen fehl, wird eine Instanz mit Standardwerten zurück geliefert.</remarks>
Public Shared Function LoadFromFile(Path As String) As AnyAppSettings
Dim Reader As StreamReader = Nothing
Try
Reader = New StreamReader(Path)
Dim Serializer As XmlSerializer = New XmlSerializer(GetType(AnyAppSettings))
Return Serializer.Deserialize(Reader)
Catch
Return New AnyAppSettings
Finally
If Not Reader Is Nothing Then
Reader.Close()
End If
End Try
End Function
End Class
Man legt eine Klasse an, in der alle notwendigen Einstellungen als öffentliche Properties hinterlegt sind, entweder als automatisch implementierte Properties oder mit einem expliziten Getter & Setter. Diesen Eigenschaften sollte man Standardwerte (Defaults) zuweisen. Über diese Properties könne dann die Einstellungen abgerufen oder gesetzt werden.
Zusätzlich werden zwei Methoden eingefügt, die das Speichern und zurückladen der Daten mit Hilfe eines XmlSerializer erledigen. Beim Einladen werden gespeicherte, aber aktuell nicht mehr vorhandene Eigenschaften ignoriert. Noch nicht in der Datei enthaltene Eigenschaften bekommen ihren Standardwert zugewiesen.
Will man dann später wieder doch wieder in AppData speichern, geht dies über Application.LocalUserAppDataPath. Dieser Pfad verweist auf den Ordner <user>/AppData/local/<app-name>/<app-version>/.
Application-Properties (Pfade, etc.)| Application-Properties (Pfade, etc.)
So löst Visual Studio die Application-Eigenschaften unter Windows 10 auf. Die Applikation heißt "AppDisplay". Das Programm wurde im Debug-Modus gestartet, der Solution-Ordner heißt "AppDisplaySln" der Projekt-Ordner heißt "AppDisplaySln".
Application … | |
---|---|
.CommonAppDataPath | C:\ProgramData\AppDisplay\AppDisplay\1.0.0.0 |
.ExecutablePath | <user>\AppData\Local\Temporary Projects\AppDisplay\bin\Debug\AppDisplay.EXE (wenn das Projekt noch nicht gespeichert wurde) |
<user>\Documents\Visual Studio 2015\Projects\AppDisplaySln\AppDisplayPrj\bin\Debug\AppDisplay.EXE | |
.LocalUserAppDataPath | <user>\AppData\Local\AppDisplay\AppDisplay\1.0.0.0 |
.StartupPath | <user>\AppData\Local\Temporary Projects\AppDisplay\bin\Debug (wenn das Projekt noch nicht gespeichert wurde) |
<user>\Documents\Visual Studio 2015\Projects\AppDisplaySln\AppDisplaySln\bin\Debug | |
.UserAppDataPath | <user>\AppData\Roaming\AppDisplay\AppDisplay\1.0.0.0 |
.CommonAppDataRegistry | HKEY_LOCAL_MACHINE\Software\AppDisplay\AppDisplay\1.0.0.0 |
.UserAppDataRegistry | HKEY_CURRENT_USER\Software\AppDisplay\AppDisplay\1.0.0.0 |
.SafeTopLevelCaptionFormat | {1} - {0} - {2} |
Label-Größe an Text anpassen | Label-Größe an Text anpassen
Mithilfe der AutoSize-Eigenschaft eines
Label-Steuerelements, dass dieses die passende Größe erhält. Standardmäßig wird die
Breite (width-Eigenschaft) des Labels
passend berechnet.
Will man jedoch erreichen, dass die Breite des Labels eine bestimmte
Größe nicht überschreitet muss man die width-Komponente der
MaxSize-Eigenschaft entsprechend einstellen. Die
BorderStyle-Eigenschaft muss auf None
stehen, sonst klappt es nicht. Der waagerechte Strich ist in Realität ein Label mit
den gleichen Eigenschaften, außer der BorderStyle-Eigenschaft,
wie das links daneben.
Achtung aufpassen: in der Entwicklungsumgebung erscheint das Label
jedoch korrekt:
Die Size-Eigenschaft wird dann automatisch entsprechend der Vorgaben angepasst.
Je nach dem, wie der Text umgebrochen werden kann, weicht die berechnete Breite von der vorgegeben ab. Im Beispiel ist das 1 Pixel. Dieser Effekt kann u.U. störend sein, z.B. wenn das Label eingefärbt werden soll und an ein anderes Steuerelement angrenzt. Dann gibt es je nach Text unterschiedlich große Lücken zwischen dem Label und dem anderen Steuerelement. Hier hilft folgender Trick.
Das sichtbare Label soll exakt die Breite W haben, die Höhe soll aber so gewählt werden, dass der zugewiesene Text vollständig angezeigt wird. Man nehme ein zusätzliches, unsichtbares Label zu Hilfe. Diesem weist man die MaxSize-Eigenschaft
HiddenLabel.MaxSize = New Size(W, 0)
und danach den umzubrechenden Text
HiddenLabel.Text = "Dies ist ein langer Text"
Danach kann man das sichtbare Label passend einstellen:
VisibleLabel.Size = New Size(W, HiddenLabel.Height)
Fenster in den Vordergrund bringen| Fenster in den Vordergrund bringen
Das geht recht einfach:
Public Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Boolean
...
SetForegroundWindow(Handle)
TextBox scrollen | TextBox scrollen / Textbox ans Ende scrollen
Ans Ende scrollen geht so:
Public Const WM_VSCROLL As Integer = &H115
Public Const SB_BOTTOM As Integer = 7
Public Declare Auto Function SendMessage Lib "user32" (hWnd As IntPtr,
wMsg As Integer, wParam As IntPtr, lParam As IntPtr) As Integer
...
SendMessage(TextBox1.Handle, WM_VSCROLL, CType(SB_BOTTOM, IntPtr), IntPtr.Zero)
Im Internet wird häufig diese Methode empfohlen
Diese Methode hat den Nachteil, dass je nach Art der TextBox (z.B. RichTextBox) der markierte Text unangenehm flackert. Die oben präsentierte Methode vermeidet diesen Nachteil.
Weitere Richtungen kann man der folgenden Aufstellung entnehmen:
Enum VScrollCommands
SB_LINEUP = 0
SB_LINEDOWN = 1
SB_PAGEUP = 2
SB_PAGEDOWN = 3
SB_THUMBPOSITION = 4
SB_THUMBTRACK = 5
SB_TOP = 6
SB_BOTTOM = 7
End Enum
WM_VSCROLL message auf MSDN.
RichTextBox SuspendLayout| RichTextBox SuspendLayout
RichTextBox.SuspendLayout hat keine Wirkung (Framework-Version 4.6.1, Windows 10). Dieser Code funktioniert:
Private Const WM_USER As Integer = &H400
Private Const EM_SETEVENTMASK As Integer = (WM_USER + 69)
Private Const WM_SETREDRAW As Integer = &HB
Private OldEventMask As IntPtr
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Private Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Public Sub BeginUpdate()
SendMessage(tbOutput.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero)
OldEventMask = SendMessage(tbOutput.Handle, EM_SETEVENTMASK, IntPtr.Zero, IntPtr.Zero)
End Sub
Public Sub EndUpdate()
SendMessage(tbOutput.Handle, WM_SETREDRAW, CType(1, IntPtr), IntPtr.Zero)
SendMessage(tbOutput.Handle, EM_SETEVENTMASK, IntPtr.Zero, OldEventMask)
End Sub
Gefunden auf hier auf stack overflow
Wenn man mit geschachtelten Aufrufen zu rechnen hat, ist diese Implementierung geeigneter:
Private UpdateCount As Integer = 0 ' Zählt die Schachtelung von Aufrufen mit.
''' <summary>
''' Unterbricht vorübergehend die Layoutlogik für das Steuerelement.
''' </summary>
Public Sub BeginUpdate()
UpdateCount += 1
If UpdateCount = 1 Then
SendMessage(tbOutput.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero)
OldEventMask = SendMessage(tbOutput.Handle, EM_SETEVENTMASK, IntPtr.Zero, IntPtr.Zero)
End If
End Sub
''' <summary>
''' Nimmt die übliche Layoutlogik wieder auf und erzwingt optional ein sofortiges ausführen der
''' ausstehenden Layoutanforderungen.
''' </summary>
''' <param name="performLayout">True, um ausstehende Layoutanforderungen auszuführen, andernfalls False.
''' Der Strandardwert ist True.</param>
Public Sub EndUpdate(Optional performLayout As Boolean = True)
UpdateCount -= 1
If UpdateCount <= 0 Then
UpdateCount = 0
SendMessage(tbOutput.Handle, WM_SETREDRAW, CType(1, IntPtr), IntPtr.Zero)
SendMessage(tbOutput.Handle, EM_SETEVENTMASK, IntPtr.Zero, OldEventMask)
If performLayout Then
tbOutput.Refresh()
End If
End If
End Sub
UTC-Timestamp => Date und zurück| UTC-Timestamp => Date und zurück
Erstaunlich einfach:
Const UnixStartDate As DateTime = #1/1/1970#
''' <summary>
''' Konvertiert ein Unix-Timestamp (Sekunden seit 1.1.1970 in Zeitzone UTC)
''' in ein <see cref="Date"/>-Objekt mit der lokalen Ortszeit.
''' </summary>
''' <param name="UtcTimestamp">Ein Unix-Timestamp (Sekunden seit 1.1.1970 in Zeitzone UTC).</param>
''' <returns>Ein <see cref="Date"/>-Objekt mit der lokalen Ortszeit.</returns>
''' <remarks>Entspricht i.W. der C-Funktion char* ctime (const time_t * timer);</remarks>
Public Function VbCTime(ByVal UtcTimestamp As UInt32) As Date
Return DateAdd("s", UtcTimestamp, UnixStartDate).ToLocalTime
End Function
''' <summary>
''' Konvertiert ein <see cref="Date"/>-Objekt in der lokalen Ortszeit
''' in ein Unix-Timestamp (Sekunden seit 1.1.1970 in Zeitzone UTC).
''' </summary>
''' <param name="NetDate">Das <see cref="Date"/>-Objekt, das konvertiert werden soll.</param>
''' <returns>Ein Unix-Timestamp, das dem <see cref="Date"/>-Objekt entspricht.</returns>
Public Function VbUnixTime(NetDate As Date) As UInt32
Return CUInt((NetDate.ToUniversalTime - UnixStartDate).TotalSeconds)
End Function
Soll's bei UTC als Zeitzone bleiben: ToLocalTime entfernen.
Unerwünschte Eigenschaften in abgeleiteten Controls verbergen| Unerwünschte Eigenschaften in abgeleiteten Controls verbergen
Will man Standard-Eigenschaften in abgeleiteten Steuerelementen verbergen, muss man die Eigenschaft mit entsprechenden Attributen versehen. Im folgenden Beispiel wird die Eigenschaft MinimumSize verborgen:
<System.ComponentModel.Browsable(False),
System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never),
System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden),
System.ComponentModel.Bindable(False)>
Public Overrides Property MinimumSize As Size
String.Split()| String.Split()
Die Methode String.Split() unterteilt eine Zeichenfolge anhand von anzugebenden Separatoren in ein Array von Teilzeichenfolgen. Dabei ist zu beachten, dass, wenn ein Separator am Ende oder am Anfang des zu splittenden Strings steht, implizit ein Leerstring vor- bzw. nachher angefügt wird. D.h. die Länge des zurückgegebenen Arrays ist immer Anzahl der enthaltenen Separatoren + 1. Hier ein paar Beispiele; der Separator ist "X".
Ausgangsstring | Array-Größe | Array-Elemente |
---|---|---|
"" | 1 | "" |
"X" | 2 | "", "" |
"XX" | 3 | "", "", "" |
"abcd" | 1 | "abcd" |
"Xabcd" | 2 | "", "abcd" |
"abXcd" | 2 | "ab", "cd" |
"abXXcd" | 3 | "ab", "", "cd" |
"XabXcd" | 3 | "", "ab", "cd" |
"abcdX" | 2 | "abcd", "" |
"XabcdX" | 3 | "", "abcd", "" |
"XabXcdX" | 4 | "", "ab", "cd", "" |
"XXabXXcdXX" | 7 | "", "", "ab", "", "cd", "", "" |
Wird der Parameter StringSplitOptions mit RemoveEmptyEntries belegt, werden alle Array-Elemente entfernt, die einen Leerstring enthalten.
Lokale IP-Adressen ermitteln| Lokale IP-Adressen ermitteln
Typischerweise besitzt ein PC mehrere lokale IP-Adressen. Dies ändert sich je nach dem, welche Hard- und Software man installiert hat. Eine Übersicht erhält man über den Befehl
ipconfig /all
in der Eingabeaufforderung:
Unter .NET kommt man mit der Methode NetworkInterface.GetAllNetworkInterfaces() weiter. Diese Methode liefert alle Netzwerk-Schnittstellen des PCs. Bei mir sind dies acht an der Zahl. Um eine bestimmte zu finden, benutzt man am besten die Name-Eigenschaft zur Selektion. Die Namen der Netzwerkverbindungen erhält man über die Systemsteuerung:
Will man Netzwerk-Schnittstellen eines bestimmten Typs auswählen, kann man die anderen Eigenschaften der ermittelten Schnittstellen-Objekte auswerten. Z.B. liefert eine Selektion des NetworkInterfaceType.Wireless80211 alle WLAN-Adapter. Aber Vorsicht, hier werden auch virtuelle Adapter ausgewählt, z.B. der "Microsoft Hosted Network Virtual Adapter". Aktive Interfaces selektiert über die Eigenschaft OperationalStatus mit dem Wert OperationalStatus.Up.
Die folgende Grafik zeigt die von NetworkInterface.GetAllNetworkInterfaces() gelieferten Objekte. Das Objekt, dass zum WLAN gehört, ist aufgeblättert.
Anmeldung an eine UDP-Muliticast-Gruppe| Anmeldung an eine UDP-Muliticast-Gruppe|
Will man sich an einer UDP-Multicast-Gruppe anmelden, muss man berücksichtigen, dass der PC i.d.R. mehr als ein Netzwerk-Interface besitzt (s. oben: Lokale IP-Adressen ermitteln). Deshalb muss man anderweitig dafür sorgen, dass das richtige Interface genutzt werden, oder die Methoden nutzen, die die lokale IP-Adresse mitgeben.
UdpClient.JoinMulticastGroup(GroupIP, LocalIp)
ist solch eine Methode.
VB.Net-Klassen aus XML- oder JSON-Dateien | VB.Net-Klassen aus XML- oder JSON-Dateien|
Visual Studio (VS2017) bietet eine einfache Möglichkeit aus einem XML- oder ein JSON-Dokument ein passende Klassenstruktur zu generieren. Dazu kopiert man den Inhalt eines Muster-Dokuments in den Zwischenspeicher, positioniert den Cursor einer Code-Datei in VS an die Stelle, an die die Klassenstruktur eingefügt werden soll und lässt dann mit dem Menü-Punkt "Bearbeiten"->"Inhalte einfügen" die Klassenstruktur generieren.
Die Datei lässt sich dann einfach einlesen. Hier ein Bespiel bei dem eine GPX-Datei als Basis genommen wurde. Der umhüllende Typ (Klasse) hat den Namen gpx. Eingelesen wird in das Objekt gpxDoc.
Dim gpxDoc As gpx
Dim reader As New System.Xml.Serialization.XmlSerializer(GetType(gpx))
Dim file As New System.IO.StreamReader(filePath)
gpxDoc = CType(reader.Deserialize(file), gpx)
Standard-Dialoge positionieren| Standard-Dialoge positionieren
Die Standard-Dialoge in .NET, z.B. der ColorDialog zur Auswahl von Farben, erleichtern das Erstellen von Anwendungen enorm. Leider lassen sich nicht alle Eigenschaften über das Standard-API einstellen. Die Dialoge werden z.B. immer in der Bildschirmmitte platziert. Auf die hier beschriebene Weise erhält man Zugriff auf weitere Merkmale der Dialoge.
Alle Standard-Dialoge sind von der Klasse CommonDialog abgeleitet. CommonDialog definiert die geschützte Methode HookProc. die überschrieben werden kann, um einem Standarddialogfeld spezifische Funktionen hinzuzufügen. Sie entspricht i.W. der WndProc für Fenster.
In der Version 4.5 des .NET Frameworks besitzt die Methode folgenden Code
Protected Overridable Function HookProc(ByVal hWnd As IntPtr, ByVal msg As Integer,
ByVal wparam As IntPtr, ByVal lparam As IntPtr) As IntPtr
If (msg = &H110) Then ' WM_INITDIALOG
CommonDialog.MoveToScreenCenter(hWnd) ' ===== Positioniert das Fenster =====
Me.defaultControlHwnd = wparam
UnsafeNativeMethods.SetFocus(New HandleRef(Nothing, wparam))
ElseIf (msg = 7) Then ' WM_SETFOCUS
UnsafeNativeMethods.PostMessage(New HandleRef(Nothing, hWnd), &H451, 0, 0)
ElseIf (msg = &H451) Then
UnsafeNativeMethods.SetFocus(New HandleRef(Me, Me.defaultControlHwnd))
End If
Return IntPtr.Zero
End Function
In Zeile 4 steht der Aufruf der Methode MoveToScreenCenter, die den Dialog auf die Bildschirmmitte platziert. MoveToScreenCenter ruft SetWindowPos zum Positionieren auf:
SetWindowPos(hWnd, 0, x, y, 0, 0, &H15)
Der Wert &H15 für den Wert von Flags ergibt sich aus der Kombination von SWP_NOACTIVATE, SWP_NOZORDER und SWP_NOSIZE. Die Anweisung wirkt sich also nur auf die Position des Fensters aus.
Wenn man den Standard-Dialog ableitet, kann man auf HookProc überschreiben und dem Dialogfenster eine neue Position geben:
Public Class LocatableColorDialog
Inherits ColorDialog
Private Location As Point
Protected Overrides Function HookProc(ByVal hWnd As IntPtr, ByVal msg As Integer,
ByVal wparam As IntPtr, ByVal lparam As IntPtr) As IntPtr
MyBase.HookProc(hWnd, msg, wparam, lparam)
If (msg = &H110) Then 'WM_INITDIALOG
SetWindowPos(hWnd, IntPtr.Zero, Location.X, Location.Y, 0, 0, &H15)
End If
Return IntPtr.Zero
End Function
Public Shadows Function ShowDialog(Location As Point) As DialogResult
Me.Location = Location
Return MyBase.ShowDialog
End Function
End Class
SetWindowPos hat folgende Signatur:
Private Declare Function SetWindowPos Lib "user32.dll" (
ByVal hWnd As IntPtr,
ByVal hWndInsertAfter As IntPtr,
ByVal X As Integer,
ByVal Y As Integer,
ByVal cx As Integer,
ByVal cy As Integer,
ByVal uFlags As Integer) As Boolean