• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

[C# i XAML] Odświeżanie danych w TextBlockach poprzez Binding - czy jest potrzebne jakieś dodatkowe ustawienie?

Object Storage Arubacloud
0 głosów
503 wizyt
pytanie zadane 3 stycznia 2018 w C# przez Duch003 Początkujący (440 p.)

Witam.

Piszę aplikację która porównuje konkretne rzeczywiste statystyki komputera z danymi z bazy danych w excelu. Pobrane dane są składowane w kilku klasach: Computer, Mainboard, RAM, Storage, SWM. Tworzę po dwie osobne instancje tych klas i odpowiednio binduję każde pole do odpowiedniego TextBlocku. Aplikacja działa w porządku, przy uruchomieniu pobiera dane z komputera za pomocą WMI jak i z excela i wypisuje zestawienie.

Problem pojawia się kiedy chcę wczytać inne dane z pliku excel - pomimo że kod zdarzenia się wykonuje, dane w kontrolkach nie odświeżają się i nie mam zielonego pojęcia dlaczego. Udostępniam te klasy które sądzę że są potrzebne do analizy problemu. Nie mogę dodać wszystkiego w jednym poście, bo przekraczam 15k znaków, więc dodam w kolejnych postach.

Moje pytanie brzmi: jak poprawnie zbindować kontrolki do konkretnych właściwości aby odświeżały się przy każdej zmianie zaznaczenia modelu w tabeli? Ustalenie Mode na TwoWay i UpdateSourceTrigger na PropertyChanged, zmiana kontrolki na TextBox nie daje efektu.

<Grid x:Name="spReader" Grid.Row="0" Grid.Column="2">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <!--51 wierszy - 2 wiersze połaczone w jeden, brak jednego separatora-->
                        <!--==============================OGÓLNE DANE==============================-->
                        <Label Content="Dane z pliku" 			    Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Center" FontSize="15" FontWeight="Bold" Background="LightSkyBlue" Padding="5"/>
                        <Separator                                  Grid.Row="1" Grid.Column="0"/>
                        <TextBox x:Name="test" Text="{Binding Komputer.MD, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 	Grid.Row="2" Grid.Column="0" HorizontalAlignment="Center" 		FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                  Grid.Row="3" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.MSN}" 	Grid.Row="4" Grid.Column="0" HorizontalAlignment="Center" 		FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                  Grid.Row="5" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.System}" Grid.Row="6" Grid.Column="0" HorizontalAlignment="Center" 		FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                  Grid.Row="7" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.Kolor}" 	Grid.Row="8" Grid.Column="0" HorizontalAlignment="Center" 		FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                  Grid.Row="9" Grid.Column="0"/>

                        <!--==============================SWM Z DYSKÓW==============================-->
                        <Label Content="SWM" 							Grid.Row="10" Grid.Column="0" HorizontalContentAlignment="Center"   FontSize="15" FontWeight="Bold" Padding="5" Background="LightSkyBlue"/>
                        <Separator                                      Grid.Row="11" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.Swm[0].Swm}" Grid.Row="12" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                      Grid.Row="13" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.Swm[1].Swm}" Grid.Row="14" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                      Grid.Row="15" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.Swm[2].Swm}" Grid.Row="16" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                      Grid.Row="17" Grid.Column="0"/>

                        <!--==============================WEAR LEVEL==============================-->
                        <Label Content="Max Wear Level" 				                                Grid.Row="18" Grid.Column="0" HorizontalContentAlignment="Center"   FontSize="15" FontWeight="Bold" Padding="5" Background="LightSkyBlue"/>
                        <Separator                                                                      Grid.Row="19" Grid.Column="0"/>
                        <TextBlock Text="{Binding Path=Komputer.WearLevel[0], StringFormat={}{0}%}"     Grid.Row="20" Grid.Column="0" HorizontalAlignment="Center" 	        FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                                                      Grid.Row="21" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.WearLevel[0], StringFormat={}{0}%}"          Grid.Row="22" Grid.Column="0" HorizontalAlignment="Center" 	        FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                                                      Grid.Row="23" Grid.Column="0"/>

                        <!--==============================DANE PŁYTY GŁÓWNEJ==============================-->
                        <Label Content="Płyta główna" 					    Grid.Row="24" Grid.Column="0" HorizontalContentAlignment="Center"   FontSize="15" FontWeight="Bold" Padding="5" Background="LightSkyBlue"/>
                        <Separator                                          Grid.Row="25" Grid.Column="0"/>
                        <TextBlock Text="{Binding PlytaGlowna.Model}"   	Grid.Row="26" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="27" Grid.Column="0"/>
                        <TextBlock Text="{Binding PlytaGlowna.ID}" 		    Grid.Row="28" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="29" Grid.Column="0"/>
                        <TextBlock Text="{Binding PlytaGlowna.WersjaBios}"  Grid.Row="30" Grid.Column="0" HorizontalAlignment="Center" 	        FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="31" Grid.Column="0"/>

                        <!--==============================RAM==============================-->
                        <!--UWAGA! Ram zajmuje tutaj dwa miejsca - dwa wiersze-->
                        <Label Content="Pamięć RAM" 					Grid.Row="32" Grid.Column="0" HorizontalContentAlignment="Center" FontSize="15" FontWeight="Bold" Background="LightSkyBlue"/>
                        <Separator                                      Grid.Row="33" Grid.Column="0"/>
                        <!--Height ustawiony na sztywno - 2 * 16(wysokosc automatyczna) + 5 * 4(padding z dwóch textBlocków z góry i z dołu) + 5 (???)-->
                        <TextBlock Text="{Binding Ram.Pojemnosc, StringFormat={}{0}GiB}" 		Grid.Row="34" Grid.Column="0" HorizontalAlignment="Center" 	TextAlignment="Justify"	FontSize="12" FontWeight="Bold" Height="57" VerticalAlignment="Center" Grid.RowSpan="2" Padding="5"/>
                        <Separator                                      Grid.Row="36" Grid.Column="0"/>

                        <!--==============================DYSKI TWARDE==============================-->
                        <Label Content="Dyski" 							                        Grid.Row="37" Grid.Column="0" HorizontalContentAlignment="Center"   FontSize="15" FontWeight="Bold" Padding="5" Background="LightSkyBlue"/>
                        <Separator                                                              Grid.Row="38" Grid.Column="0"/>
                        <TextBlock Text="{Binding Dyski[0].Pojemnosc, StringFormat={}{0}GB}" 	Grid.Row="39" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                                              Grid.Row="40" Grid.Column="0"/>
                        <TextBlock Text="{Binding Dyski[1].Pojemnosc, StringFormat={}{0}GB}" 	Grid.Row="41" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                                              Grid.Row="42" Grid.Column="0"/>

                        <!--==============================POZOSTAŁE DANE==============================-->
                        <Label Content="Pozostałe" 						    Grid.Row="43" Grid.Column="0" HorizontalContentAlignment="Center"   FontSize="15" FontWeight="Bold" Padding="5" Background="LightSkyBlue"/>
                        <Separator                                          Grid.Row="44" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.Obudowa}" 	    Grid.Row="45" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="46" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.ShippingMode}"   Grid.Row="47" Grid.Column="0" HorizontalAlignment="Center" 	        FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="48" Grid.Column="0"/> 
                        <TextBlock Text="{Binding Komputer.LCD}" 		    Grid.Row="49" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                        <Separator                                          Grid.Row="50" Grid.Column="0"/>
                        <TextBlock Text="{Binding Komputer.PelnyModel}"     Grid.Row="51" Grid.Column="0" HorizontalAlignment="Center" 		    FontSize="12" FontWeight="Bold" Padding="5"/>
                    </Grid>

 

komentarz 3 stycznia 2018 przez Duch003 Początkujący (440 p.)
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Retriever
{
    /// <summary>
    /// Logika interakcji dla klasy MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public Reader reader;
        ThisComputerInfo thisComputerInfo;
        public MainWindow()
        {
            InitializeComponent();
            PrzygotujWiazanie();
        }

        void PrzygotujWiazanie()
        {
            //Wczytanie i przypisanie listy modeli do grida
            reader = new Reader();
            gridModele.ItemsSource = reader.listaModeli;
            spReader.DataContext = reader;

            //Wczytanie danych rzeczywistych komputera i przypisanie do gridów
            thisComputerInfo = new ThisComputerInfo();
            spNaglowki.DataContext = spComputerInfo.DataContext = thisComputerInfo;
            
            //Kod wyszukiwarki modeli
            CollectionView widok = (CollectionView)CollectionViewSource.GetDefaultView(gridModele.ItemsSource);
            widok.SortDescriptions.Add(new SortDescription("Model", ListSortDirection.Ascending));
            widok.Filter = FiltrUzytkownika;

            //Próba określenia modelu i automatycznego wczytania danych konkretnrgo modelu do zestawienia
            var q = from item in reader.listaModeli
                    where item.MD.Equals(thisComputerInfo.Komputer.MD)
                    select item;
            reader.ReadData((q.First() == null) ? (gridModele.SelectedItem as Model)  : (q.First() as Model));
        }

        bool FiltrUzytkownika(object item)
        {
            if (String.IsNullOrEmpty(txtWyszukiwarka.Text)) return true;
            else return (((item as Model).MD.IndexOf(txtWyszukiwarka.Text, StringComparison.OrdinalIgnoreCase) >= 0));
            //Wyszukiwanie również po msn: else return (((item as Model).MD.IndexOf(txtWyszukiwarka.Text, StringComparison.OrdinalIgnoreCase) >= 0) || ((item as Model).MSN.IndexOf(txtWyszukiwarka.Text, StringComparison.OrdinalIgnoreCase) >= 0));
        }

        //Metoda wyszukiwania z listy modeli
        void txtWyszukiwarka_TextChanged(object sender, TextChangedEventArgs e)
        {
            CollectionViewSource.GetDefaultView(gridModele.ItemsSource).Refresh();
        }

        //Metoda wczytująca dane aktualnie zaznaczonego rekordu
        private void gridModele_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            reader.ReadData(gridModele.SelectedItem as Model);
        }
    }
}
        FileStream stream;
        IExcelDataReader excelReader;
        DataSet result;
        public Computer Komputer { get; set; }
        public RAM Ram { get; private set; }
        public Storage[] Dyski { get; private set; }
        public Mainboard PlytaGlowna { get; private set; }
        public IEnumerable<Model> listaModeli { get; private set; }
        public string Plik = @"/NoteBookiRef_v2.xlsx";
using System.Collections.Generic;

namespace Retriever
{
    public class Computer
    {
        public string MD { get; set; } //Model komputera
        public string MSN { get; private set; } //MSN komputera
        public string System { get; private set; } //System operacyjny
        public SWM[] Swm { get; private set; } //SWM na dyskach
        public double[] WearLevel { get; private set; } //Przegrzanie baterii
        public string Wskazowki { get; private set; } //Dodatkowe informacje odnośnie komputera
        public string Obudowa { get; private set; } //Model obudowy
        public bool ShippingMode { get; private set; } //ShippingMode
        public string NowyMSN { get; private set; } //NowyMSN
        public string LCD { get; private set; } //Rodzaj matrycy
        public string PelnyModel { get; private set; } //Dotyczy peaq - pełny model z dodatkowymi informacjami
        public string Kolor { get; private set; } //Kolor obudowy

        public Computer(string md = "Brak", string msn = "Brak", string system = "Brak", SWM[] swm = null, double[] wearLevel = null,
            string wskazowki = "BraK", string obudowa = "Brak", string lcd = "Brak", string kolor = "Brak", bool shipp = false, string nowyMsn = "Brak",
            string pelnyModel = "Brak")
        {
            MD = md;
            System = system;
            Swm = (swm == null) ? new SWM[1] { new SWM() } : swm;
            WearLevel = (wearLevel==null) ? new double[1] { -999 } : wearLevel;
            Wskazowki = wskazowki;
            Obudowa = obudowa;
            ShippingMode = shipp;
            NowyMSN = nowyMsn;
            LCD = lcd;
            PelnyModel = pelnyModel;
            Kolor = kolor;
        }
    }

    public class Mainboard
    {
        public string Model { get; private set; } //Model płyty głównej
        public string Producent { get; private set; } //Producent płyty
        public string CPU { get; private set; } //Procesor
        public string Taktowanie { get; private set; } //Taktowanie
        public string WersjaBios { get; private set; } //Wersja bios płyty
        public string ID { get; private set; } //Pełna nazwa procesora

        public Mainboard(string model = "-", string producent = "Brak", string cpu = "Brak", string taktowanie = "Brak", string bios = "Brak")
        {
            Model = model;
            Producent = producent;
            CPU = cpu;
            Taktowanie = taktowanie;
            WersjaBios = bios;
            ID = string.Format($"{CPU}" + " @ " + $"{Taktowanie}");
        } 
    }
    public class RAM
    {
        public string Bank { get; private set; } //Slot
        public double Pojemnosc { get; private set; } //Pojemność
        public string Info { get; private set; }

        //Konstruktor standardowy
        public RAM(double size = 0, string bank = "0")
        {
            Bank = bank;
            Pojemnosc = size;
            Info = string.Format($"{bank}: {Pojemnosc}");
        }

        //Konstruktor na potrzeby bazy danych w pliku excel
        public RAM(string info)
        {
            //Przeszukuje zakres możliwych wartości pamięci ram
            for (int i = 33; i > 0; i--)
            {
                if (info.Contains(i.ToString()))
                {
                    Pojemnosc = i;
                    Bank = "0";
                    return;
                }
            }
            //W innym wypadku
            Pojemnosc = 0;
            Bank = "Brak";
        }
    }

    public class Storage
    {
        public string Nazwa { get; private set; } //Nazwa dysku
        public double Pojemnosc { get; private set; } //Pojemność
        List<string> size = new List<string>
        {
            "1",
            "1,5",
            "1.5",
            "2",
            "16",
            "32",
            "64",
            "128",
            "240",
            "256",
            "512",
            "750"
        };
        //Konstruktor standardowy
        public Storage(double size = 0, string nazwa = "Brak")
        {
            Nazwa = nazwa;
            Pojemnosc = size;
        }

        //Konstruktor na potrzeby bazy w pliku excel
        public Storage(string info)
        {
            info.ToLower();
            for (int i = size.Count-1; i >= 0; i--)
            {
                //Badanie czy info zawiera w sobie którąś z wartości w liście
                if (info.Contains(size[i]))
                {
                    //Dodatkowy warunek uwzględniający pamięć typu M.2
                    if (size[i] == "2" && (info.Contains("m2") || info.Contains("m.2"))) continue;
                    else
                    {
                        Pojemnosc = double.Parse(size[i]) <= 2 ? double.Parse(size[i])*1000 : double.Parse(size[i]);
                        Nazwa = info.Replace(size[i], "").Replace("GB", "").Replace("TB", "").Replace(" ",""); //Usuwa wartość oraz jednostkę, pozozstawia sam typ pamięci.
                        return;
                    }
                }
            }
            //W innym wypadku
            Pojemnosc = 0;
            Nazwa = info;
        }
    }

    public class SWM
    {
        public string Dysk { get; private set; }
        public string Swm { get; private set; }

        public SWM(string dysk = "-", string swm = "00000000")
        {
            Dysk = dysk;
            Swm = swm;
        }
    }
}
public class Model
    {
        public int Wiersz { get; set; } //Nr linii danego modelu
        public int Zeszyt { get; set; } //Nr zeszytu na ktorym jest model (MD/PQ)
        public string MSN { get; set; }
        public string MD { get; set; }
        public string NazwaZeszytu { get; set; }
        public Model(int wiersz, int zeszyt, string msn, string md, string sheetName)
        {
            Wiersz = wiersz;
            Zeszyt = zeszyt;
            MSN = msn;
            MD = md;
            NazwaZeszytu = (sheetName == "MD") ? "Medion" : "Peaq";
        }
    }

 

1 odpowiedź

+1 głos
odpowiedź 3 stycznia 2018 przez maciej.tokarz Nałogowiec (27,280 p.)

Cześć,

a nie możesz skorzystać z kolekcji obserwowalnych? Dawno w .Net nie pisałem, ale jak pamiętam klasa powinna implementować INotifyPropertyChanged (warto skorzystać z Fody).

M.

komentarz 3 stycznia 2018 przez Duch003 Początkujący (440 p.)
Co do kolekcji obserwowalnych: jeżeliby chodziło o listę to tak, jak najbardziej, jednak ta lista służy jedynie do wybrania konkretnego modelu z listy, następnie w polach klasy reader są zapisywane odpowiednie dane i to z tych pól nie aktualizują mi się dane.

 

Sprawdzę Fody. Dam znać co wyszło.
komentarz 3 stycznia 2018 przez Duch003 Początkujący (440 p.)

EDIT: Znalazłem rozwiązanie problemu. Prawdopodobnie podczas wpisywania nowych danych do pól binding nie działa bo w jakiś sposób zmienia się instancja, więc instancję należy przypisać na nowo. Tak zmodyfikowałem metodę wczytującą z MainWindow.xaml.cs:

 

//Metoda wczytująca dane aktualnie zaznaczonego rekordu
        private void gridModele_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            reader = new Reader();
            reader.ReadData(gridModele.SelectedItem as Model);
            spReader.DataContext = reader;
            
        }

Temat do zamknięcia.

Podobne pytania

0 głosów
1 odpowiedź 287 wizyt
pytanie zadane 15 marca 2017 w C# przez PejtaM Użytkownik (550 p.)
0 głosów
2 odpowiedzi 226 wizyt
pytanie zadane 17 września 2019 w C# przez seba Dyskutant (8,900 p.)
0 głosów
1 odpowiedź 153 wizyt
pytanie zadane 15 września 2016 w C# przez jankustosz1 Nałogowiec (35,880 p.)

92,551 zapytań

141,399 odpowiedzi

319,530 komentarzy

61,937 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...