Jak z innej klasy przekazać dane do obiektów formatki.

0

Witam,

nie mogę sobie poradzić z powyższym zadaniem. Potrzebuję wyświetlić stan licznika, który "leci" w osobnej klasie.
Startuję klasę Nonameclass w której działa sobie timer. Chciałbym wyświetlić jego wartość w textboxie, który znajduje się na formatce Form1.

Dziekuję za pomoc.


```namespace Test_CSharp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Run_Nonameclass();
        }

        private void Run_Nonameclass()
        {
            Nonameclass newNonameclass = new Nonameclass();
        }
    }
}


namespace Test_CSharp
{
    class Nonameclass
    {

        private System.Timers.Timer mycounter;
        public int cnt;
        Form1 frm = new Form1();

        public Nonameclass()
            {

            cnt = 0;

            mycounter = new System.Timers.Timer();
            mycounter.Interval = 1000;
            mycounter.Start();
            mycounter.Elapsed += mycounter_Tick;

        }

        private void mycounter_Tick(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                cnt = cnt + 1;
                frm.textBox1.Text = cnt.ToString();        
            }
            catch (Exception ex)
            {
            }

        }
    }
}
0

Komendą Form1 frm = new Form1(); tworzysz referencję do nowo utworzonego obiektu Form1 czyli dublujesz sobie te formy.
Jeżeli chcesz przekazać z innej klasy coś do Form1 to musisz utworzyć w konstruktorze odwołanie do tego obiektu już istniejącego.

 public partial class Form1 : Form
  {
  public static Form1 form1;

  public Form1()
        {
            InitializeComponent();
        
           form1 = this;
        }

Potem w tej drugiej klasie

 try
            {
                cnt = cnt + 1;
                Form1.form1.textBox1.Text = cnt.ToString();        
            }
0

Ja czegoś nie rozumiem: Tworzysz Form1 w Nonameclass oraz Nonameclass w Form1. Coś tu pogmatwałeś.
Ale jeżeli masz instancję Nonameclass w Form1 i chcesz pobierać liczbę jak się zmieniła możesz do tego użyć zdarzeń:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            newNonameclass = new Nonameclass();
            newNonameclass.TextChanged += TextChanged;
            newNonameclass.StartTimer();
        }

        private void TextChanged(object sender, EventArgs e)
        {
            Dispatcher?.Invoke(() =>
            {
                MyTextBlock.Text = (sender as Nonameclass).cnt;
            });
        }

        Nonameclass newNonameclass;
        
    }
    class Nonameclass
    {
        private System.Timers.Timer mycounter;
        public int cnt { get; private set; }
        public Nonameclass()
        {
            cnt = 0;
           
            
        }
        public void StartTimer()
        {
            mycounter = new System.Timers.Timer();
            mycounter.Interval = 1000;
            mycounter.Elapsed += mycounter_Tick;
            mycounter.Start();
        }
        public event EventHandler<EventArgs> TextChanged;
        private void mycounter_Tick(object source, System.Timers.ElapsedEventArgs e)
        {
            ++cnt;
            TextChanged?.Invoke(this, EventArgs.Empty);
        }
    }
0

@Marik10: Dzięki za podjęcie tematu Marik10. Właśnie zrobiłem próbę, ale niestety nie poszło.
Coś dalej mam źle.

prtscr1.png

0

Było wiele razy nie można zmieniać stanu UI bezpośrednio z innego wątku. Trzeba użyć Invoke. Poszperaj

0

@jacek.placek: No właśnie szperam i wyszperać nie mogę. Cały czas wyrzuca mi błędy.

Pozdrawiam

0

@gswidwa1: Serdeczne dzięki za odpowiedź.
Chyba tego jednak nie ogarnę.
prtscr2.png

0

Form1.form1.Invoke=>(Form1.form1.textBox1.Text = cnt.ToString());
Jakoś tak to leciało.

1

Piszesz coś na czuja zupełnie bez sensu. Musisz poczytać i zrozumieć zdarzenia i delegaty bo to są podstawy.

Zdefiniuj klasę danych zdarzenia

public class TextChangedEventArgs
{
	public string Text { get; set; }
}

Nonameclass, podobnie jak u Ciebie, ma delegat i event, oraz metody start i stop do uruchamiania i zatrzymywanie timera. W timer_elapsed event wyzwalany jest z obiektem TextChangedEventArgs.

public class Nonameclass
{
	public delegate void TextChangedEventHandler(object sender, TextChangedEventArgs e);
	public event TextChangedEventHandler OnTextChanged;

	private int counter { get; set; } = 0;


	System.Timers.Timer timer = null;
	public Nonameclass()
	{
		timer = new System.Timers.Timer();
		timer.Elapsed += timer_elapsed;
		timer.Interval = 1000;
		
	}

	public void Start()
	{
		timer.Start();
	}

	public void Stop()
	{
		timer.Stop();
	}

	private void timer_elapsed(object sender, ElapsedEventArgs e)
	{
		counter++;
		this?.OnTextChanged(this, new TextChangedEventArgs { Text = $"text changed {counter}" });
	}
}

W Form jest label1 i 2 przyciski Start i Stop

public partial class Form1 : Form
    {
		Nonameclass t = new Nonameclass();

        public Form1()
        {
            InitializeComponent();
            t.OnTextChanged += T_OnTextChanged;
        }

        private void T_OnTextChanged(object sender, TextChangedEventArgs e)
        {
			this.Invoke((MethodInvoker)delegate() { label1.Text = e.Text; });
            
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
			t.Start();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
			t.Stop();
        }
    }
0

Witam ponownie,

zrobiłem coś takiego, podpatrując jeden przykład na forum. Trochę "na około" i nie do końca pewnie zgodnie ze sztuką, ale działa. Z klasy Nonameclass zrobiłem formatkę. I generalnie jestem w stanie wywoływać teraz procedurę write_to_textbox, która znajduje się w 'obrębie' docelowej formatki i przepisywać wartość licznika z klasy Nonameclass. Nie rozumiem tylko jednej rzeczy. Jeśli dodam na formatkę przycisk z na zasadzie drag and drop, to zdarzenie kliknięcia na nim prawidłowo przepisuje mi wartość licznika, który sobie gdzieś tam w tle leci. Natomiast timer 'mycounter' wrzucony tak jak to zrobiłem powoduje komunikat 'System.InvalidOperationException', mimo tego, że działa i modyfikuje zmienną cnt. Zrobiłem jeszcze jedną próbę, i wrzuciłem timer na zasadzie drag and drop (tak jak przycisk). I on też działa prawidłowo. Jaka jest różnica pomiędzy oba timerami?

Pozdrawiam Wszystkich obecnych!


using System;
using System.Windows.Forms;

namespace Test_CSharp
{
    public partial class Form1 : Form
    {
 
        private Nonameclass newNonameclass;

        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            Run_Nonameclass();
        }

        private void Run_Nonameclass()
        {
            newNonameclass = new Nonameclass(this);
            newNonameclass.Show();
            System.Threading.Thread.Sleep(500);
        }

        public void write_to_textbox(string text)
        {
                this.textBox2.Text = text;
        }

        public void write_to_textbox2(string text)
        {
            this.textBox3.Text = text;
        }

    }
}


using System;
using System.Windows.Forms;

namespace Test_CSharp
{
    public partial class Nonameclass: Form
    {

        public System.Timers.Timer mycounter;
        public int cnt;
        public int cnt2;
        private Button button1;
        private Timer timer1;
        private System.ComponentModel.IContainer components;
        private Form1 virtualform;

        public Nonameclass(Form1 virtualform)
        {
            InitializeComponent();
            this.virtualform = virtualform;
            cnt = 0;

            mycounter = new System.Timers.Timer();
            mycounter.Interval = 1000;
            mycounter.Start();
            mycounter.Elapsed += mycounter_Tick;

        }

        private void mycounter_Tick(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                cnt = cnt + 1;
                virtualform.write_to_textbox(cnt.ToString());
            }
            catch (Exception ex)
            {
            }
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.button1 = new System.Windows.Forms.Button();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(37, 51);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(121, 54);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // timer1
            // 
            this.timer1.Enabled = true;
            this.timer1.Interval = 1000;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            // 
            // Nonameclass
            // 
            this.ClientSize = new System.Drawing.Size(274, 229);
            this.Controls.Add(this.button1);
            this.Name = "Nonameclass";
            this.Load += new System.EventHandler(this.Nonameclass_Load);
            this.ResumeLayout(false);

        }

        private void button1_Click(object sender, EventArgs e)
        {
            virtualform.write_to_textbox(cnt.ToString());
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            try
            {
                cnt2 = cnt2 + 1;
                virtualform.write_to_textbox2(cnt.ToString());
            }
            catch (Exception ex)
            {
            }
        }

    }
}

0

To dlatego, że licznik aby nie zawiesić Ci aplikacji działa w oddzielnym wątku, a kliknięcie na przycisk uwalnia zdarzenie w ramach tego samego wątku. Event this.timer1.Tick += new System.EventHandler(this.timer1_Tick); przychodzi z innego wątku i z niego próbujesz zmieniać wartości

private void timer1_Tick(object sender, EventArgs e)
        {
            try
            {
                cnt2 = cnt2 + 1;
                virtualform.write_to_textbox2(cnt.ToString());
            }
            catch (Exception ex)
            {
            }
        }

Musisz użyć Control.Invoke(). Spowoduje to, że przeniesiesz operacje do swojego wątku i wszystko będzie działać cacy.
Tutaj masz jak to zrobić https://stackoverflow.com/questions/6650691/invoke-in-windows-forms
Dodatkowo wklej cały Exception jaki Tobie wyskakuje (treść), bo samo invalidOperationException mało mówi i jedynie po kodzie stwierdzam, co może być nie tak.


Zasymulowałem zmianę tytułu okna po upływie 1s:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            timer = new System.Timers.Timer();
            timer.Interval = 1000;
            timer.Elapsed += Timer_Elapsed;
            timer.Start();
        }
        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.Title = "Nowy tytuł okna. próbuje zmienić coś w UI";
        }
        private System.Timers.Timer timer;
    }
Zgłoszony wyjątek: „System.InvalidOperationException” w WindowsBase.dll

A teraz z użyciem Dispatcher.Invoke() (u Ciebie Control.Invoke())

        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Dispatcher?.Invoke(() =>
            {
                this.Title = "Nowy tytuł okna. próbuje zmienić coś w UI";
            });
        }

SCREEN

screenshot-20210323131833.png

0

@Almatea: Wywal to co ostatnio wkleiłeś.

timer1 jest z System.Windows.Forms.Timer
mycounter jest z System.Timers.Timer
To są 2 różne timery. Pierwszy działa w wątku UI a drugi w osobnym.

Napisałem Ci wcześniej cały potrzebny i działający poprawnie kod. Czego w nim nie rozumiesz?

To Ci działa bo ten timer jest z Windows.Forms i działa w wątku UI więc ten event ma normalny dostęp do UI

private void timer1_Tick(object sender, EventArgs e)
        {
            try
            {
                cnt2 = cnt2 + 1;
                virtualform.write_to_textbox2(cnt.ToString());
            }
            catch (Exception ex)
            {
            }
        }

To nie działa bo mycounter jest z SystemTimers.Timer i używa osobnego wątku do odliczania

private void mycounter_Tick(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                cnt = cnt + 1;
                virtualform.write_to_textbox(cnt.ToString());
            }
            catch (Exception ex)
            {
            }
        }

W ogóle skasuj ten cały projekt bo IMO próbujesz cos pozlepiać z tego co już masz zamiast zacząć z czystą głową.
Napisz klasę danych zdarzenia (jakiś TimerElapsedEventArgs) i wrzuć tam to co chcesz, wartość licznika albo od razu cały tekst.
Napisz klasę Nonameclass z uruchamianiem timera, zdarzeniem on_tick, eventem i delegatem.
A potem to sobie obsłuż w Form.

0

@gswidwa1: Dzięki serdeczne, temat licznika ogarnąłem. Napisałem program realizujacy to samo zadanie w VB.NET. Zajęło mi to jakieś 10 minut. Nie mogę jednak sobie z tym poradzić w C#. Konwerter nie do końca działa dobrze. Tworzę klasę publiczną NoNameclass pod nazwą myNonameclass. Dokładam zdarzenie. A w klasie odwołuję się bezpośrednio poprzez tą procedurę. I to działa bez problemu. Nie wiem jak oprogramować to RaiseEvent w C#. W VB kod jest naprawdę banalny i prosty. Nie przypuszczałem, że w C# będzie to takie skomplikowane.

Public Class Form1

    Public myNomameclass As NoNameclass

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        myNomameclass = New NoNameclass
        AddHandler myNomameclass.WRITELABEL, AddressOf Me.OVERWRITELABEL

    End Sub

    Public Sub OVERWRITELABEL(ByVal sender As NoNameclass, ByVal text As String)
        Me.Label1.Text = text
    End Sub

End Class





Public Class NoNameclass

    Private cnt As Integer
    Private tmr As System.Windows.Forms.Timer
    Private WithEvents myTimer As New System.Windows.Forms.Timer()
    Public Event WRITELABEL(ByVal sender As NoNameclass, ByVal str As String)

    Private frm1 As Form1

    Sub New()

        tmr = New System.Windows.Forms.Timer
        tmr.Interval = 1000
        tmr.Start()
        AddHandler tmr.Tick, AddressOf tmr_Tick

    End Sub

    Private Sub tmr_Tick(myObject As Object, ByVal myEventArgs As EventArgs) Handles myTimer.Tick

        cnt = cnt + 1
        RaiseEvent WRITELABEL(Me, cnt.ToString)

    End Sub

End Class

0

No jeżeli nie stosujesz rad które daje Ci ja lub jacek to nic dziwnego. Dostałeś rozwiązania na tacy. Teraz wystarczy poświęcić trochę czasu i to ogarnąć zamiast robić śmietnik w kodzie wklejając różne wycinki z różnych części internetu

0

Bo teraz w Nonameclass użyłeś timera z Windows.Forms.Timer a wcześniej miałeś timer z System.Timers.Timer. Czy Ty w ogóle zauważasz takie różnice?

0

Nie trzeba zauważać. To zostało napisane, ale mam czasami wrażenie że nikt tego nie czyta, a nawet jak czyta to nie analizuje. Miał dwa rozwiązania na tacy, nie skorzystał z żadnego

0

@jacek.placek: Tak, zauważam. Z całym szacunkiem - nawet sam o tym napisałem i dopytywałem o różnice pomiędzy tymi timerami. Ale to podobno ja nie czytam treści.

0

To dlaczego dostając dwa rozwiązania i wytłumaczenie problemu nie zrobiłeś tego?

0

@gswidwa1: Zastosowałem rady, ale niestety nie zadziałały (prtscr w załączniku). Wysłałem nawet @jacek.placek cały projekt po wprowadzonych zmianach. Więc próbuję rozwiązać sam problem. Tak czy inaczej dzięki serdeczne wszystkim za pomoc. Nie przypuszczałem, że w VB to będzie zadanie na 5 minut, a w C# tak skomplikowane. Rzekłbym nawet - niemożliwe. Kończę temat, bo szkoda czasu.

screenshot-20210323151243.png

1

Ech... :)
Przydałaby się jakaś blacklista tutaj :)

Chyba brakuje CI przypisania obsługi zdarzenia w form

public Form1()
        {
            InitializeComponent();
            t.OnTextChanged += T_OnTextChanged; // <<<<<<<<<<<<<<
        }

[edit]

Można dodać jeszcze Invoke to, jeśLi nie będzie delegata to się nie wysypie. Dla @Almatea nie ma ratunku ale może komuś się przyda bo ten temat powraca.

private void timer_elapsed(object sender, ElapsedEventArgs e)
	{
		counter++;
		this?.OnTextChanged?.Invoke(this, new TextChangedEventArgs { Text = $"text changed {counter}" }); // <<< Tu Invoke
	}

@Tasmanian Devil mnie prześladuje :(

TimerTest1.zip

0

@jacek.placek: Oj tam, oj tam. Nie takie przypadki ratowano ;) Trochę wiary w człowieka ;) Ja całe życie pisałem w VB 6.0, później VB.NET, trochę Delphi, do tego wszelkiej maści sterowniki PLC. I tak ciężko mi było z marszu zabrać się za C#. Mam jeszcze pytanie, bo nie rozumiem idei tej klasy 'TextChangedEventArgs' i tego wywołania "this?.OnTextChanged(this, new TextChangedEventArgs { Text = $"text changed {counter}" });". Możesz mi to jakoś wyoślić? Z góry dzięki. Nie można wywołać bezpośrednio jakiejś procedury umieszczonej w klasie Form1 po zdarzeniu timera w klasie Nonameclass? Tak jak ja to robię pod VB.NET?
W załączniku to samo realizujący projekt w VB.NET gdyby ktoś chiał.

RaiseEvent.zip

0

@gswidwa1: Dzięki serdeczne! Pozdrawiam wszystkich obecnych.

2

Jeśli chcesz użyć timera, który ma własny wątek to nie można. UI może być zmieniane tylko z jego własnego wątku.

TextChangedEventArgs jest klasą z danymi zdarzenia. Taki pojemnik na dane, które chcesz przekazać w zdarzeniu. Jak definiujesz jakieś zdarzenie to tam powinny być dane specyficzne dla tego zdarzenia.
Dla Click Buttona masz

private void btnStart_Click(object sender, EventArgs e). 

To EventArgs to jest odpowiednik TextChangedEventArgs mojego zdarzenia. U mnie tam jest tylko tekst ale możesz dodać np. DateTime czas {get;set} i będziesz miał w zdarzeniu czas zdarzenia.
Czyli

public class TextChangedEventArgs
{
    public string Text { get; set; }
public DateTime czas { get; set; }
}

Oczywiście Text i czas trzeba ustawić jak wywołujesz zdarzenia

this?.OnTextChanged?.Invoke(this, new TextChangedEventArgs { Text = $"text changed {counter}", czas = DateTime.Now });

Ta linia wywołuje zdarzenia. Jeśli jakaś inna klasa będzie miała przypisaną metodę do obsługi tego zdarzenia. Dodatowo Tu Invoke po ?. powoduje, że jeśli nikt nie będzie nasłuchiwał tego zdarzenia to nie wywali błędu Object reference...

Nonameclass t = new Nonameclass();
t.OnTextChanged += T_OnTextChanged; // << subskrybuje zdarzenie OnTextChanged metodą T_OnTextChanged

Czyli w Nonameclass to

this?.OnTextChanged?.Invoke(this, new TextChangedEventArgs { Text = $"text changed {counter}", czas = DateTime.Now });

Wywołuje nam w Form to

private void T_OnTextChanged(object sender, TextChangedEventArgs e)
{
            this.Invoke((MethodInvoker)delegate() { label1.Text = e.Text; });

}

bo mamy subskrypcję na zdarzenie: t.OnTextChanged += T_OnTextChanged;

I tu trzeba użyć Invoke dla kontrolki (może to być dla Form - this - jak u mnie lub dla jakiejś konkretnej kontrolki np. Label) bo to zdarzenie przychodzi z innego wątku niż wątek UI.

To powoduje niejako synchronizację wątku UI z wątkiem wywołującym zdarzenie. Pewnie fachowo to się jakoś inaczej nazywa.

I po co to się robi? Ano po to, żeby NonameClass nie musiała mieć wprost zależności do Form. Nanameclass nie powinna nic wiedzieć o Form i nie definiować referencji do Form. Nonameclass coś tam sobie robi i jak chce kogoś innego o czymś poinformować (np. że timer tiknął to uruchamia zdarzenie. Jeśli ktoś będzie nasłuchiwał tego zdarzenia, a może to być kilka różnych innych obiektów, to sobie to zdarzenie obsłużą po swojemu. UI pokaże cos w Labelu a inny obiekt może zapisać do pliku.

Zobacz, ze w Designerze formy dla Buttona masz np .cos takiego

this.button1.Click += new System.EventHandler(this.button1_Click);

ten System.EventHandler to też jest delegat.

public delegate void EventHandler(object sender, EventArgs e);

A Button przez dziedziczenie po Control ma zdefiniowane zdarzenie

public event EventHandler OnClick;

Czyli jest sobie delegat (w namespace System)

public delegate void EventHandler(object sender, EventArgs e);

i Button ma po Control dziedziczony event

public event EventHandler OnClick;

delegat jest określany jako wskaźnik na funkcję więc Designer przypisuje obsługę

this.button1.Click += new System.EventHandler(this.button1_Click);

1 użytkowników online, w tym zalogowanych: 0, gości: 1