Problem z dostępem do kontrolek z poziomu innej klasy

0

Witam. Jak można się odwołać do kontrolek z MainWindow z poziomu innej klasy? Przykładowo mamy statycznie dodany button1 oraz klasę A, która ma metodę funkcja(). Jak w jej ciele zmienić Content buttona1 na "zmieniono" ?

MainWindow.button1.Content = "zmieniono"; 

Powyższy zapis zwraca błąd [CS0120] An object reference is required for the non-static field, method, or property.

3

Najprościej przekazać tego buttona jako argument metody: public void Method(Button button). Przekazujesz klasę, która jest typem referencyjnym. To trochę tak jakbyś przekazywał w C przez wskaźnik. Zmiany zostaną zapisane w oryginale.

Co do samej idei: przekazywanie kontrolek to zły pomysł. Zmiana contentu kontrolki powinna został wyzwolona jakimś zdarzeniem i dokonana wewnątrz "własnej formy".

Najlepiej skorzystać z event aggragator'a ale to wymaga od Ciebie zaznajomienia się z czymś takim jak framework Prism (polecam).
Bez tego możesz przekazać do okna nadrzędnego np. akcję, którą wykonasz w tymże oknie, a ona zmieni Ci content w rodzicu: public void Method(Action action).

Kolejna kwestią, a w zasadzie podstawową jest potrzeba posiadania statycznego buttona, co uważam za bezsensowne. O_o. Uwierz mi: nie chcesz tego tak robić.

pseudokod

public class Child
{
	private Action action;
	public Child(Action action)
	{
		this.action = action;
	}

	public void DoSomething()
	{
		// Tutaj odświeżasz content przycisku w oknie rodzica.
		action?.Invoke();
	}
}
public class Parent
{
	private void ChangeButtonText()
	{
		//...
	}

	public void ShowChildWindow()
	{
		var child = new Child(ChangeButtonText);
		// lub:
		var child = new Child(()=>ChangeButtonText());
		child.ShowDialog();
	}
}

Dostęp public, private wedle życzenia: już nie chce mi się zmieniać :)

0

Czy jest możliwość wskazania że dany button stworzony w xaml ma być statyczny? W tej chwili jestem w stanie określić statyczność buttona tylko przy tworzeniu operatorem new.

2

Program to zestaw współpracujących ze sobą obiektów, dotyczy to zwłaszcza elementów GUI - każdy ekran, kontrolka czy przycisk musi być oddzielnym obiektem. Jaki niby byłby cel statycznego buttona? Jak miałby działać przycisk wspólny dla stu ekranów danego rodzaju? Skąd miałby wiedzieć na którym ekranie i jaką operację wykonać?
To tak, jakby wszyscy ludzie mieli jedną wspólną wątrobę.

2

@Erwin14: zastanawiam się dlaczego usilnie starasz się przeforsować pomysł z tym statycznym buttonem, pomimo prób wyperswadowania Ci tego. Zastanawiam się też czy aby na pewno wiesz o czym piszesz i czy static Button jest rzeczywiście tym co chcesz uzyskać. Moim skromnym zdaniem na pewno nie.

0

Właściwie to chodzi mi o static Image. Zakładam że sprawienie że będzie statyczny jest analogiczne jak przy buttonie. Używam tylko jednego okna więc nie przeszkadza że z tego co rozumiem ten image pojawił by się we wszystkich, lecz rozumiem że to zła praktyka.

A czy jest możliwość wywołania niestatycznej metody klasy MainWindow z poziomu innej klasy?

2

A ten image to jakiś obrazek po prostu? Bo takie rzeczy zazwyczaj się w jakichś resourcach trzyma.

Wywołanie metody niestatycznej jest banalne, musisz tylko najpierw utworzyć obiekt.

0

To obiekt typu Image. Jego zawartość jest obrazkiem png dodanym do resource. Ale to nie istotne, dlatego dla uproszczenia mówiłem o buttonie.

A co do niestatycznej metody - nie wiem czy zwróciłeś uwagę, chodzi mi o metodę w klasie MainWindow. Powiedzmy że mam drugą klasę A, a w niej metodę która wywołuje metodę z klasy MainWindow tak jak poniżej. Zazaczyłem miejsce w którym zwraca błąd.

--------MAINWINDOW.XAML.CS -------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void napisz(int a)
        {
            button.Content = a;
        }
    }
}
--------A.CS----------------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication3
{
    class A
    {
        int x;
        int y;
        
        A(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public void wywolaj()
        {
            MainWindow.napisz(this.x); // ZWRACA BŁĄD CS0120 An object reference is required for the non-static field, method or property
        }
    }
}
---------------------------------------------------------------------------------------------------
2

To bez znaczenia, klasa to klasa:

var mw = new MainWindow();
mw.Metoda();

Tylko Ty naprawdę powinieneś poprawnie użyć zasobów zamiast tak kombinować.

0

Próbuję stworzyć instancję klasy MainWindow jak napisałeś, lecz program się nie kompiluje. W którym miejscu mam ją stworzyć?

XAML:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication3"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="25,25,0,0" VerticalAlignment="Top" Width="75"/>

    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindow MW;

        public MainWindow()
        {
            InitializeComponent();
            MW = new MainWindow();
        }

        public void napisz(int a)
        {
            button.Content = a;
        }
    }
}

A.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication3
{
    class A
    {
        int x;
        int y;
        MainWindow my;
        
        A(int x, int y, MainWindow my)
        {
            this.x = x;
            this.y = y;
            this.my = my;
        }

        public void wywolaj()
        {
            my.napisz(this.x);
        }
    }
}

Wkrótce przerobię pierwszy post grześka51114 . Jednak teraz zadowoliłoby mnie łatwiejsze, mniej eleganckie rozwiązanie.

1

Z tego co widzę, to rekurencyjnie wołasz konstruktor. Zanim skończy się tworzyć obiekt MainWindow każesz go utworzyć znowu i tak w nieskończoność. (Tzn. dopóki stos się nie skończy.)

Napisz może jaki efekt docelowy chcesz osiągnąć, co zobaczyć na ekranie, bo na 100% da się to zrobić łatwiej. Wydaje mi się, że wystarczyłoby przekazać już utworzony obiekt MainWindow do klasy lub metody, w której chcesz coś z nim zrobić i nie potrzebowałbyś niczego statycznego.

2

@Erwin14: Hmm... odnoszę naprawdę wrażenie, że nie wiesz co robisz, ponieważ nie znasz podstaw obiektówki. @somekind ma rację. Jaki sens ma ten fragment kodu?

        MainWindow MW;

        public MainWindow()
        {
            InitializeComponent();
            MW = new MainWindow();
        }
```
Próbowałeś to wykonać? W najlepszym wypadku dostaniesz tutaj StackOverflowException, ponieważ tworzysz w nieskończoność instancje `MainWindow`. Konstruktor odpala się samoczynnie podczas tworzenia obiektu, a Ty każesz wewnątrz utworzyć obiekt tego samego typu więc ponownie odpalasz samoczynnie jego konstruktor i tak w kółko aż do wyczerpania stosu.

Dwa: nie musisz tworzyć jakichś pól prywatnych z kontrolkami takimi jak `MainWindow`, ponieważ obie klasy są w jednej przestrzeni nazw `WpfApplication3` więc są wzajemnie widoczne. Starczy, że zrobisz gdziekolwiek w metodach `var window = new SomeWindow();` i obiekt zostanie utworzony. Jak już pisałem wcześniej: całych kontrolek, choć tak naprawdę "referencji do nich" się nie przekazuje, bo to nie ma sensu. Czemu zwyczajnie nie zastosujesz akcji czy eventu, który odpalisz po aktualizacji wartości, tak jak Ci pokazałem?

Trzy: no i ten image... Do tego są "resursy". Klikasz PPM na projekt, wybierasz `Add`, dajesz `New Item...`. Później wybierasz coś co się nazywa `Resources File` i dopiero tam dodajesz obrazki. Dalej klikasz na ten plik dwa razy, ustawiasz na liście rozwijanej, że chcesz `Images`, robisz `Add Resource`, a następnie `Add Excisting File` i masz obrazek dostępny **w całej** aplikacji. Starczy wpisywać ścieżkę w konrolkach. I nie potrzebujesz **ŻADNYCH** staticów, uwierz mi na słowo.

Naprawdę... podejdź może najpierw do zrozumienia podstaw C#, tego jak tworzy się obiekty, jak działają konstruktory etc. Będzie Ci prościej, bo jak patrzę na inne Twoje posty to odnoszę wrażenie, że uczysz się C# trochę od tyłu: chcesz poznać język robiąc apki w WPF. Trochę nie tędy droga uważam, bo co rusz będziesz potykał się o przypadki takie jak to nieszczęsne rekurencyjne tworzenie instancji.
1

WYJAŚNIAJĄC:
Ad. 1 akapit. Próbowałem to wykonać. Efekt był taki jak napisałeś.
Ad. 3. Co do Image Resource to wiem to. Image był static ponieważ tak: chciałem wywołać metodę z mainwindow z poziomu innej klasy -> dostałem błąd że coś tam non-static field -> więc zmieniłem tą metodę na static -> to znów błąd, tamta metoda korzystała właśnie z image-> to zmieniłem image na static i działa.
Ad 4. Wcześniej programowałem sporo w C++ Builder 6. To moja pierwsza aplikacja C#. Chcę zrobić grę typu obrona wieży (celem gry jest budowanie wieżyczek by ochronić bazę przez wrogami). W tej chwili program wygląda tak:
title
Naciśnięcie START powoduje że czołgi zaczynają jechać. Naciśnięcie na szary kwadracik powoduje że w tym miejscu powstaje działko (na razie bez ograniczeń, docelowo będzie za golda). Działka atakują automatycznie najbliższy cel. Te żółte strzałki to właśnie te nieszczęsne statyczne Image. Odpowiadają za skręcanie czołgu. Docelowo będą niewidoczne. Są następujące klasy: Tank - te jeżdżące czołgi, Cannon - działko, Bullet - pocisk wystrzeliwany z działka.
Wszystkie pliki projektu: http://erwin2.ayz.pl/gra/ i tam pierwszy link z góry.

2

Nie powiem, bo ciekawie to zrobiłeś. Pamiętam jak napisałem kiedyś tetrisa w WPF na podstawie kontrolki DataGrid z kolei :) Generalnie WPF tak średnio nadaje się do gier. W wielu miejscach się też powtarzasz np to występuje w kodzie chyba N razy:

            img_dir4_1 = new Image();
            img_dir4_1.Width = 50;
            img_dir4_1.Height = 20;
            img_dir4_1.Source = new BitmapImage(new Uri(@"dir4.bmp", UriKind.Relative));
            img_dir4_1.HorizontalAlignment = HorizontalAlignment.Left;
            img_dir4_1.VerticalAlignment = VerticalAlignment.Top;
            margin_tmp = img_dir4_1.Margin;
            margin_tmp.Left = 50;
            margin_tmp.Top = 100;
            img_dir4_1.Margin = margin_tmp;
            maingrid.Children.Add(img_dir4_1);
```

Wrzuć takie rzeczy do jednej metody i obiekty, które się zmieniają przekazuj przez argument. W C# nie musisz się martwić o przekazywanie przez referencje czy wartość, bo typy takie jak `Image` są z definicji referencyjne. To dużo pomaga i upraszcza. Gdybyś miał zrobioną jedną metodę zamiast powtarzać się, chyba z pięć razy, byłoby dużo prościej.

```csharp
Tank[] tank = new Tank[10];
```
Takie coś możesz zamienić np: na `List<Tank>`. Mógłbyś wtedy dynamicznie zmieniać ilość czołgów. Nie byłbyś ograniczony do dziesięciu. Lista to generalnie "reprezentacja" tablicy w C#, czyli coś takiego jak `Vector` w C++.

```csharp
public class Cannon :MainWindow
```
Nie wiem czy ta klasa nie jest za ciężka i powiem Ci, że raczej nie powinna dziedziczyć po MainWindow. To powinna być osobna kontrolka np: UserControl. U Ciebie to dziedziczenie jest uważam w ogóle bez sensu i do niczego się nie przydaje, bo chyba nie korzystasz nawet z tego, że ta klasa to tak naprawdę `MainWindow` :)

Widać takie C++'sowe nawyki generalnie: jakieś dziwne dziedziczenia, kompletnie niepotrzebne, przedkładanie tablic ponad listy czy czasami przekazywanie różnych dziwnych argumentów :)

Niemniej powiem Ci, że nawet jest grywalność, autentycznie :D
Mnie by się drugi raz jednak gry w WPF robić nie chciało.

PS: Baaaaardzo dużo pomoże Ci poznanie podstaw języka w ogóle. Teraz robisz wiele rzeczy trochę od tyłu.
PS2: Tego Grid'a możnaby rozmieszczać jakoś dynamicznie, bo tak to masz tylko jedną planszę dostępną ale to pewnie i tak wiesz.
0

Jeśli chodzi o dziedziczenie z MainWindow to po prostu zapomniałem tego usunąć, po tym jak coś kombinowałem. Już to poprawiłem.
Powtarzający się kod o którym mówisz tworzy te strzałki do skręcania. Zrobię metodę skracającą.
Nigdy Vector czy List nie używałem, muszę się douczyć.
Zmienność plansz zostawiam na koniec. W tej chwili pracuję nad działkami tj. zamierzam zrobić różne rodzaje działek i możliwość ich ulepszania.
Dziękuję za pomoc i miłe słowa o projekcie. Pozdrawiam ; )

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