Laden...
M
musicmiles myCSharp.de - Member
Elektrotechniker Hessen Dabei seit 03.11.2015 7 Beiträge
Benutzerbeschreibung

Forenbeiträge von musicmiles Ingesamt 7 Beiträge

25.01.2016 - 16:40 Uhr

Hallo zusammen,

ich schreibe derzeit an einem kleinen Programm, welches dynamisch Buttons einem TableLayoutPanel hinzufügen und mit verschiedenen Funktionen belegen kann (eine Art eigenes Software-Keyboard). Dabei wird beim Starten des Programms ein Button-Array mit so vielen Buttons, wie es Elemente in dem tableLayoutPanel gibt, initialisiert. Diese werden dann per Klick und Kontektmenu dem TableLayoutPanel hinzugefügt. Die Buttons können auch einzeln ebenso wieder entfernt werden.
Um eine einmal eingerichtete Oberfläche (mit verschiedenen Buttons gefülltes TableLayoutPanel) nicht immer wieder einrichten zu müssen, wird das ganze als *.xml-file gespeichert und auch wieder geladen. Das hinzufügen und speichern funktioniert auch ohne Probleme.

Folgendes funktioniert leider nicht:
Wenn ich einen Button entferne, tue ich das über ein KontextMenü. Der Handler sieht folgendermaßen aus:


private void toolStripMenuItemRemoveButton_Click(object sender, System.EventArgs e)
{
    MeinToolStripMenuItem l_tsp = sender as MeinToolStripMenuItem;
            
    l_tsp.Button.Text = l_tsp.Button.Name;			// setzt die individuelle Beschriftung zurück
    l_tsp.Button.Row = -1;					// setzt die Position auf -1
    l_tsp.Button.Column = -1;					// setzt die Position auf -1
    tableLayoutPanelKeys.Controls.Remove(l_tsp.Button);		// entfernt den button aus dem TableLayoutPanel
}

Diese Methode entfernt offensichtlich auch den Button aus dem Panel. In der GUI ist er nicht mehr zu sehen!

Vor dem Schließen der Bearbeitung wird noch folgende Methode ausgeführt um alle Buttons im TableLayouPanel zu "finden" und um deren Position im *.xml-File abzuspeichern.


private void CheckTableLayoutForButtons()
{
	TableLayoutPanelCellPosition positionOfBtn = new TableLayoutPanelCellPosition();

       	// Check for Buttons
        for(int i = 0; i<Buttons.Length; i++)
        {
        	positionOfBtn = tableLayoutPanelKeys.GetPositionFromControl(Buttons[i]);
            Buttons[i].Row = positionOfBtn.Row;
            Buttons[i].Column = positionOfBtn.Column;
        }
        SaveLayoutToFile();
}

Wenn ich mir die letzte Methode mit dem Debugger ansehe, stelle ich fest, dass im Button-Array vor folgender Zeile Column und Row auf -1 stehen(wie gewollt).


positionOfBtn = tableLayoutPanelKeys.GetPositionFromControl(Buttons[i]);

Nach den nächsten beiden Zeilen steht wieder die alte Position (vor dem entfernen: Bspw.: column 2, row 4) in den Column- und Row-Eigenschaften meines Buttons.


Buttons[i].Row = positionOfBtn.Row;
Buttons[i].Column = positionOfBtn.Column;

Es scheint, als müsste das TableLayoutPanel irgendwie nach dem Entfernen der Buttons aktualisiert werden. Leider habe ich bis jetzt keine Möglichkeit gefunden, dies zu tun. TableLayoutPanel.Update(), etc. bewirken nichts dergleichen (was man auch schon der Beschreibung entnehmen kann):

Hat jemand eine Idee, woran es liegt, bzw. kann mir ein Tipp geben, ob und wo ich einen Denkfehler habe?

Vielen Dank und viele Grüße

musicmiles

22.01.2016 - 14:14 Uhr

Besten Dank für die Erklärung.

Ja, da hänge ich in der Tat noch ein wenig in der Luft. Ich programmiere seid ein paar Jahren C und Assembler für Mikrocontroller und oft sind es Programme (die angepasst/verändert werden müssen), die etliche Jahre alt sind und garnicht von mir geschrieben wurden. Von daher blieb ich, meistens jedenfalls, davon verschont mir Gedanken über Namenskonventionen, etc. machen zu müssen.
Seit kurzem programmiere ich nun auch in c# (komplett eigene Projekte) und stolpere oft über den Gedankengang: "Wie nenne ich das jetzt?", und helfe mir meistens damit aus diese Tätigkeiten auf später zu verschieben. Somit passiert es auch ab und zu, dass mein jetziges Ich den vor Wochen geschriebenen Code erst analysieren muss bevor es es versteht. 😉

Kurzum: Über Tipps, Lektüre-Empfehlungen, etc. dazu bin ich dankbar. Aber das gehört ja nun nicht mehr in dieses Thema.

Danke 😃

22.01.2016 - 13:36 Uhr

Vielen Dank für die schnelle Antwort.

Also ich erzeuge einfach eine Instanzvariable vom Typ Button in meiner abgeleiteten Klasse und greife per Getter- und Setter darauf zu.


public Button btn { get; set; }


private void ButtonLayout_Clicked(object sender, System.EventArgs e)
{
    Button l_btn = sender as Button;
    toolStripMenuItemRename.btn = l_btn;
    toolStripMenuItemDefaultName.btn = l_btn;
    contextMenuStripKeyLayout.Show(l_btn, mousex, mousey);
}

So funktioniert es, nur folgender Hintergrund interessiert mich:
Es wird in diesem Fall in der Klasse "MeinToolStripMenuItem" gar kein neues Objekt angelegt, sondern lediglich auf das existierende verwiesen (referenziert)?

22.01.2016 - 11:43 Uhr

Hallo zusammen,

ich erzeuge zur Laufzeit verschiedene Buttons in einem TableLayoutPanel, indem auf ein freies Feld geklickt wird, sich ein Kontext-Menü öffnet und man dort "erzeuge Button" wählt.
Klickt man jetzt auf diesen eben oder einen zuvor erstellten Button erscheint ein weiteres Kontext-Menü mit einem Eintrag "Umbenennen". Klickt man darauf, öffnet sich ein Fenster mit einer Textbox, der Name kann eingetragen werden und mit Klick auf OK schließt das Fenster und der Button ist umbenannt.

Der Klick auf den Button öffnet den entsprechenden Event-Handler, dieser Wiederrum öffnet das Kontext-Menü und ein Klick auf den Eintrag desselbigen öffnet den Eventhandler des Eintrags. Leider ist nun das Objekt sender vom Typ "ToolStripMenuItem" und nicht mehr vom Typ Button bzw. der Button selbst.

Ich habe es derzeit so gelöst: Ich habe eine eigene Klasse "MeinToolStripMenuItem" erzeugt die von "ToolStripMenuItem" erbt. Dort habe ich eine integer-variable "ID" öffentlich über getter und setter zugänglich gemacht. Wenn nun auf den Button im Layout geklickt wird, wird dem ensprechenden ToolStripMenuItem... die "ID" des Button mitgegeben.

private void ButtonLayout_Clicked(object sender, System.EventArgs e)
{
	Button l_btn = sender as Button;
        toolStripMenuItemRename.ID = l_btn.ButtonID;
        toolStripMenuItemDefaultName.ID = l_btn.ButtonID;
        toolStripMenuItemDefaultName.Text1 = "B" + (l_btn.ButtonID+1);
        toolStripMenuItemRemoveButton.ID = l_btn.ButtonID;
        contextMenuStripKeyLayout.Show(Buttons[l_btn.ButtonID], mousex, mousey);
}

In dem eigentlichen Event-Handler greife ich dann auf diese ID zu und kann so auf den rufenden Button schließen. Ich finde die Lösung nicht sonderlich elegant, denn wenn ich demnächst im Kontext-Menü viele weitere Verzweigungen habe, wird das schnell unübersichtlich.

Gibt es eine Möglichkeit, über mehrere Event-Aufrufe das ursprüngliche "rufende" Objekt anzusprechen, bzw. Eigenschaften auszulesen?

Vielen Dank für Eure Hilfe.

musicmiles

30.11.2015 - 10:48 Uhr

Guten Morgen,

ich erstelle derzeit ein kleines Programm und nutze einige Buttons zum Steuern eines an den PC angeschlossenen Gerätes über eine serielle Schnittstelle.

Ich habe eine eigene Klasse (OwnButton) definiert, welche von der Forms-Klasse Button erbt. Im Hauptprogramm erstelle ich die Buttons mithilfe einer Schleife. Über den Ausdruck

buttons[i].Click += (sender, e) => this.EventButtonPrim(ButtonID);

erzeuge ich nur eine EventHandler-Methode und prüfe dort auf die ID des Buttons und führe dann relevanten Code aus. Da die Oberfläche über sehr viele Buttons verfügt, welche letzendlich nur einen anderen Befehl senden, wollte ich nicht für jeden Button einen eigenen Event-Handler erzeugen. Das funktioniert auch bisher einwandfrei.

Hier die Initialisierung der Buttons:


private void InitializeButtonsPrimary()
{
    buttons = new OwnButton[65];
    repeater = new ButtonClickRepeater[65];
    
    for (int i = 0; i < buttons.Length; i++)
    {
        byte ButtonID = (byte)i;

        buttons[i] = new OwnButton();
        
        repeater[i] = new ButtonClickRepeater(buttons[i], 1000);

        buttons[i].Name = "Button " + i;
        buttons[i].Text = "Text " + i;
        buttons[i].Dock = DockStyle.Fill;
        buttons[i].Margin = new Padding(0);
        buttons[i].TabStop = false;
        buttons[i].UseVisualStyleBackColor = true;
        buttons[i].Visible = true;
        buttons[i].BackColor = StudentButtonColorPrimary;
        buttons[i].Enabled = false;
        buttons[i].Font = new Font(buttons[i].Font.FontFamily, ButtonFontSize, buttons[i].Font.Style | ButtonFontStyle);
        buttons[i].Click += (sender, e) => this.EventButtonPrim(ButtonID);
    }
}

Nun möchte ich aber jeden Button um die Funktion erweitern, eine bestimmte Aktion durchzuführen, wenn dieser länger als bspw. eine Sekunde gedrückt wird. Dazu hab ich im Internet recherchiert und ein schöne Möglichkeit mithilfe von timern gefunden.
C# Long pressed Button

Dazu habe ich folgende Klasse zu meinem Projekt hinzugefügt:


class ButtonClickRepeater
{
    public event EventHandler Click;

    private Button button;
    private Timer timer;

    public ButtonClickRepeater(Button button, int interval)
    {
        if (button == null) throw new ArgumentNullException();

        this.button = button;
        button.MouseDown += new MouseEventHandler(button_MouseDown);
        button.MouseUp += new MouseEventHandler(button_MouseUp);
        button.Disposed += new EventHandler(button_Disposed);

        timer = new Timer();
        timer.Interval = interval;
        timer.Tick += new EventHandler(timer_Tick);
    }

    void button_MouseDown(object sender, MouseEventArgs e)
    {
        OnClick(EventArgs.Empty);
        timer.Start();
    }

    void button_MouseUp(object sender, MouseEventArgs e)
    {
        timer.Stop();
    }

    void button_Disposed(object sender, EventArgs e)
    {
        timer.Stop();
        timer.Dispose();
    }

    void timer_Tick(object sender, EventArgs e)
    {
        
        OnClick(EventArgs.Empty);
    }

    protected void OnClick(EventArgs e)
    {
        if (Click != null) Click(button, e);
    }
}

Und in meiner Schleife habe ich folgende Zeile hinzugefügt:

repeater[i].Click += (sender, e) => this.EventButtonPrimRepeated(ButtonID);

Als ich noch alle Click-EventHandler einzeln in meinem Programm hatte, funktionierte es problemlos zwischen kurzem und langem Klick zu unterscheiden. Erst seitdem ich meine Events in einer EventMethode (buttons_.Click += (sender, e) => this.EventButtonPrim(ButtonID);) auswerte, bekomme ich bei einem Klick auf den Button sofort beide Aktionen, d.h. beide Handler werden sofort ausgeführt. Der Timer feuert sein Event allerdings erst nach einer Sekunde (so wie gewünscht).

Kann mir bitte jemand einen Tipp geben, wo ich nach meinem Fehler suchen kann? Ich verstehe nämlich nicht, wie der Compiler die beiden Click-Events auseinanderhalten soll.

Vielen Dank und freundliche Grüße

musicmiles

10.11.2015 - 21:01 Uhr

Hallo Alf,

vielen Dank für die Antwort. Der Link hat mir sehr geholfen.

Einen schönen Abend

musicmiles

03.11.2015 - 17:03 Uhr

Guten Tag myC#-Community,

ich programmiere seid ein paar Jahren verschiedene Mikrocontroller in C. In den Objektorientierten Sprachen habe ich Erfahrungen in C++ und in Java gesammelt, dass liegt aber schon ein paar Jahre zurück.

Nun zum Thema bzw. zu meiner Frage:
Ich habe zur Kommunikation einiger Komponenten (Mikrocontroller) untereinander ein Protokoll entworfen. Dieses Protokoll hat folgenden Aufbau: Befehl, Länge, Daten[n], Checksumme. Sind neue Daten verfügbar, werden diese per Interrupt in einen Ring-Puffer geschrieben und wenn der Prozessor Zeit hat, liest dieser den Ring-Puffer aus und durchläuft Byte für Byte eine Statemaschine und interpretiert diese Daten. Nun möchte ich meine Controller mit einem Programm auf dem Computer kommunizieren lassen und habe dazu eine Forms-Anwendung erstellt. In meine Forms-Anwendung habe ich eine serielle Schnittstelle implementiert und lasse mir, wie auf den Controllern auch, einen Ringpuffer füllen.

Das Füllen des Ringpuffers klappt bisher (testweise) ohne Probleme und wird von dem Ereignis SerialPort.DataReceived folgendermaßen initiert (Code Auszugsweise):


const int MAX_SIZE_REC_QUEUE = 1024;

byte[] ReceiveQueue = new byte[MAX_SIZE_REC_QUEUE];
int RecQueueWrPtr = 0;
int RecQueueRdPtr = 0;
int BytesToRead = 0;

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
	BytesToRead = serialPort1.BytesToRead;
	serialPort1.Read(ReceiveQueue,RecQueueWrPtr,BytesToRead);
	RecQueueWrPtr += BytesToRead;
	if (RecQueueWrPtr >= MAX_SIZE_REC_QUEUE - 1)
		RecQueueWrPtr = 0;
}

Nun möchte ich die Daten in meinem Ringpuffer Byte für Byte einer Statemaschine zuführen und überlege wie ich dies in C# am saubersten gestalte. In dem Event-Handler serialPort1_DataReceived() über eine Schleife Byte für Byte durch den Puffer hangeln scheidet aus. Ich würde das gerne parallel oder besser gesagt pseudoparallel zur GUI machen. Auf meinen Controllern läuft einfach in main() eine Endlosschleife in der die entsprechende Überprüfung bei jedem Durchlauf stattfindet und bei Bedarf bei jedem Durchlauf der entsprechende Handler aufgerufen wird. Aber diese Schleife gibt es ja in meinem C#-Programm nicht.
Bei meiner Suche im Forum bzw. im Internet bin auf Begriffe wie Task, Backgroundworker, Thread, Event und Delegates gestoßen. Allerdings erscheint es mir nicht schlüssig, wie man ein solches Problem Grundsätzlich in C# angeht.
Wer kümmert sich bei einer oben genannten Lösung um den interruptfesten Zugriff auf meinen Ringpuffer, etc..

Kann mir jemand geeignete Lektüre empfehlen oder einen Denkansatz bzgl. des oben genannten Problems geben.

Vielen Dank im voraus

musicmiles