IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: EntityFramework, listbox WPF - zobrazení dat do textboxů

V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Poggy
Člen
Avatar
Poggy:24.3.2018 16:04

Ahoj,

pokouším se o svojí první vlastní aplikaci vo WPF s použítím Entity Frameworku. Mám jen takovou jednoduchou aplikaci na evidenci zkonzumovaného jídla. Nic velkého, jen jako zkouška. Mám následující problém. Níže je obrázek jak aplikace vypadá. Mám nastaven filtr pro načtení dat z databáze. Zatím je funkční jen filtr dle datumu, ale to není důležité. Po stisknutí tlačítka "Hledej" se mi správně načtou i zobrazí jednotlivá jídla. Až sem je to v pohodě.

Po načetní listboxu bych dále chtěl na události listbox.Sekec­tionChanged měnit hodnoty v textboxech níže. Tyto hodnoty poté editovat v textboxech a tlačítkem "Uložit" provést uložení do databáze. Bohužel nevím jak docílit onoho naplnění textboxů daty. Myslím jsi, že budu muset mít globální List se seznamem vybraných jídel a v něm poté dohledat zvolený item listboxu a z něj zobrazovat hodnoty. Nazval jsem si ho jako upravovanaJidla.
Vyjímky zachytávám zatím "provizorně".

Přiložím kód třídy Jidlo:

namespace Jidelnicek
{
    using System;
    using System.Collections.Generic;

    public partial class Jidlo
    {
        public int Id { get; set; }
        public string Nazev { get; set; }
        public Nullable<System.DateTime> Datum { get; set; }
        public string Alergeny { get; set; }
        public Nullable<double> Kalorie { get; set; }
        public Nullable<double> Tuky { get; set; }
        public Nullable<double> Cukry { get; set; }
        public Nullable<double> Sacharidy { get; set; }
        public string Poznamka { get; set; }
        public Nullable<int> FKkategorie { get; set; }

        public virtual Kategorie Kategorie { get; set; }

        public override string ToString()
        {
            return Nazev;
        }
    }
}

Dále kód tlačítka "Hledej":

private void btnHledejU_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                DateTime hledaneDatum = dtpDatumU.SelectedDate.Value;
                string hledanaKategorie = cbDruhJidlaU.SelectedItem.ToString();
                lbVysledkyU.Items.Clear();

                using (JidelnicekEntities context = new JidelnicekEntities())
                {
                    var jidlo = (from Jidlo in context.Jidloes where Jidlo.Datum == hledaneDatum orderby Jidlo.Nazev select Jidlo).ToList();
                    foreach (var j in jidlo)
                    {
                        lbVysledkyU.Items.Add(j);
                        upravovanaJidla.Add(j);
                    }
                }
            }
            catch (Exception)
            {
                MessageBox.Show("Něco se nepovedlo.");
            }
        }

Kód pro událost SelectionChanged

private void lbVysledkyU_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    try
    {
        using (JidelnicekEntities context = new JidelnicekEntities())
        {
            if (lbVysledkyU.SelectedItem != null)
            {
                ListBoxItem vybranaPolozka = ((sender as ListBox).SelectedItem as ListBoxItem);
                Jidlo vybraneJidlo = new Jidlo();

                // toto nefunguje, představa toho, co bych chtěl dosáhnou
                vybraneJidlo = (Jidlo)vybranaPolozka;

                // sem bych chtěl potom dodat něco jako toto, zatím jen dva textboxy:
                txtJidloU.Text = vybraneJidlo.Nazev;
                txtAlergenyU.Text = vybraneJidlo.Alergeny;
                // jen nevím jak přetypovat item v listboxu na instatnci objektu Jidlo, toto nefunguje: Jidlo vb = (Jidlo)vybraneJidlo;
            }
        }
    }
    catch (Exception)
    {
        MessageBox.Show("Něco se nepovedlo.");
    }
}

Děkuji za každou radu.

Editováno 24.3.2018 16:06
 
Odpovědět
24.3.2018 16:04
Avatar
Filip Němeček
Tvůrce
Avatar
Filip Němeček:24.3.2018 16:38

Ahoj,

využij Binding v XAML. Tvůj ListBox bude mít jako DataSource něco ve stylu {Binding = JmenoListu} a potom si buď můžeš udělat šablonu a zobrazovat v jednotlivých položkách více informací, nebo si přepsat ToString() na jméno jídla. Bylo by dobré vytvořit view model třídu, která bude mít seznam těch jídel a view model nastavíš jako DataContext pro tvoje okno, aby fungoval Binding. Pokud chceš přes Binding upravovat, tak nastav Mode na TwoWay, aby se to zpětně přepsalo.

Entity Framework si změny objektů hlídá sám, takže potom stačí zavolat Save() na tom contextu, přes který se dostáváš k datům.

Ještě musíš implementovat INotifyProper­tyChanged na tvé třídě Jidlo, aby se UI měnilo se změnami properties. Stejně tak použij ObservableCollec­tion pro seznam jídel, aby jejich mazání/přidání bylo hned promítáno do UI a nemusel jsi to manuálně nastavovat sám :-)

 
Nahoru Odpovědět
24.3.2018 16:38
Avatar
Poggy
Člen
Avatar
Odpovídá na Filip Němeček
Poggy:24.3.2018 17:37

Ahoj,

děkuji za odpověď. Bohužel prvnímu odstavci prakticky nerozumím. Nevím co mám udělat. Zatím jsem si hrál jen ve WF. Tzn., že mám dopsat do MainWindow.xaml do definice listboxu něco jako toto:

<!--ListBox s výsledky hledání-->
<StackPanel Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" >
    <ListBox x:Name="lbVysledkyU" Height="100" Margin="0 5" SelectionChanged="lbVysledkyU_SelectionChanged"
             DataSource="{Binding=upravovanaJidla}" />
</StackPanel>

To mi VS nedovolí, celý DataSource je podržený. V MainWindow.xaml.cs jsem změnil kolekci List na ObservableCollec­tion. Rozhraní se mi povedlo implementovat.

    public partial class MainWindow : MetroWindow
    {
        // slouží k uchování obejktů zvoleného jídla pro úpravu nebo smazání jídla
        ObservableCollection<Jidlo> upravovanaJidla = new ObservableCollection<Jidlo>();
        ObservableCollection<Jidlo> mazanaJidla = new ObservableCollection<Jidlo>();

        public MainWindow()
        {
            InitializeComponent();
        }
        #region Tlačítko Hledej, naplnění listboxu v gridu uprav jídlo

        private void btnHledejU_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                DateTime hledaneDatum = dtpDatumU.SelectedDate.Value;
                string hledanaKategorie = cbDruhJidlaU.SelectedItem.ToString();
                lbVysledkyU.Items.Clear();

                using (JidelnicekEntities context = new JidelnicekEntities())
                {
                    var jidlo = (from Jidlo in context.Jidloes where Jidlo.Datum == hledaneDatum orderby Jidlo.Nazev select Jidlo).ToList();
                    foreach (var j in jidlo)
                    {
                        lbVysledkyU.Items.Add(j);
                        upravovanaJidla.Add(j);
                    }
                }
            }
            catch (Exception)
            {
                MessageBox.Show("Něco se nepovedlo.");
            }
        }
        #endregion
}

Co je model view třída již bohužel netuším vůbec.

Editováno 24.3.2018 17:38
 
Nahoru Odpovědět
24.3.2018 17:37
Avatar
Filip Němeček
Tvůrce
Avatar
Odpovídá na Poggy
Filip Němeček:24.3.2018 18:00

Nevadí, já právě nevěděl, kolik toho už z WPF znáš...

Na Binding mrkni třeba sem - https://www.itnetwork.cz/…nin-bindingy Prakticky bych bez toho WPF aplikaci nedělal.

ListBox má ItemsSource místo DataSource, moje chyba :-)

View model je součástí návrhového vzoru MVVM (Model-View-ViewModel), což je takové MVC ve WPF světě. Prakticky jde pouze o to, že složitější logiku máš oddělenou v jiné tříd, aby se ti nepletla v tříde pro okno, kde máš události tlačítek, konstruktor okna, Load metodu a tak podobně. Právě tomu se říká View Model. Model jsou potom data, která získáváš z databáze a View je tvoje okno.

Takže vlasně všechny vlastnosti (v tvém případě ty ObservableCollec­tion<Jidlo>) a další bys dal do třídy MainWindowVM (jako view model) a v konstruktoru okna vytvořil instanci právě tohoto VM. Pak už právě stačí nastavit DataContext okna na tvoji vytvořenou třídu.

public partial class MainWindow : Window
    {
        private MainWindowVM _vm;

        public MainWindow()
        {
            InitializeComponent();

            _vm = new MainWindowVM();
}
}

Potom už můžeš mít logiku v MainWindowVM a všechny změny dat se ti přes nastavený Binding budou přepisovat do uživatelského rozhraní.

Mám za to, že ty ObservableCollec­tion musí být deklarované jako properties, aby fungoval Binding. Což znamená něco takového:

public ObservableCollection<Jidlo> Jidla { get; set; } = new ObservableCollection<Jidlo>();

A Binding by pak vypadal asi nějak takhle:

<ListBox ItemsSource="{Binding Jidla, Mode=TwoWay} " />
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
 
Nahoru Odpovědět
24.3.2018 18:00
Avatar
Poggy
Člen
Avatar
Odpovídá na Filip Němeček
Poggy:25.3.2018 12:03

Nebyla by jíná cesta bez Bindingu, něco jako jsem myslel původně. Bindingy nedokáži rozchodit ani dle toho tutoriálu zde na upomínači. Některé pole mi fungují a některé ne... To už si to raději budu hlídat ručně jako ve WF.

Editováno 25.3.2018 12:04
 
Nahoru Odpovědět
25.3.2018 12:03
Avatar
jozef_i
Člen
Avatar
Odpovídá na Poggy
jozef_i:25.3.2018 13:53

Ak máš v ListBoxu na Value property naviazaní Id objektu Jidlo, tak potom po zvolení položky v ComboBoxe z ListBoxItem vytiahneš Id (ako?, stačí ListBoxItem
pretypovať na triedu, s dvoma poliami Id, Nazev, ktorú si vytvoríš). A potom cez Id v tabuľke Jidloes nášdeš potrebné Jidlo.

 
Nahoru Odpovědět
25.3.2018 13:53
Avatar
Odpovídá na Poggy
Michal Štěpánek:25.3.2018 20:40

Binding je právě ta jedna z "přidaných hodnot" WPF oproti WF. Umožní ti bez problémů změnu hodnot v kontrolkách při nějaké události. Projdi si ještě jednou ten tutoriál na WPF, je to tam krásně vysvětleno. Pochopil jsem to z toho i já hňup a to je co říct... :-P

Nahoru Odpovědět
25.3.2018 20:40
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Poggy
Člen
Avatar
Poggy:25.3.2018 21:04

Děkuji za obě odpovědi. Bohužel dnes jsem neměl moc času. Čím víc nad tím přemýšlím, tím víc by to bylo lepší udělat přes binding. Dle toho tutoriálu mí funguje jen něco. Např. dnešní datum a zobrazení narozenin. Ostatní ne. Ale asi mám něco špatně. Jen to najít. Tomu se pověnuji zítra.

<Window x:Class="WpfUpominacnarozenin.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:WpfUpominacnarozenin"
        mc:Ignorable="d"
        Title="Upominac narozenin" Height="350" Width="525" Background="WhiteSmoke" WindowStartupLocation="CenterScreen">
    <Grid Margin="10" >
        <!--Hlavní rozložení gridu-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <!--Dnešní datum-->
        <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal">
            <TextBlock Text="Dnes je "/>
            <TextBlock Text="{Binding DnesniDatum, StringFormat=dd.MM.yyyy}"/>
        </StackPanel>

        <!--Nejbližší narozeni-->
        <StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
            <TextBlock Text="Nejbližší narozeniny má "/>
            <TextBlock Text="{Binding NejblizsiOsoba.Jmeno}" />
            <TextBlock Text=" za " />
            <TextBlock Text="{Binding NejblizsiOsoba.ZbyvaDni}"/>
            <TextBlock Text=" dní"/>
        </StackPanel>

        <!--ListBox osoby-->
        <ListBox Name="lbOsoby" Grid.Column="0" Grid.Row="2" Margin="0,0,0,10"
                 ItemsSource="{Binding Osoby}"/>

        <!--Kalendář + popisky na ním-->
        <StackPanel Grid.Column="1" Grid.Row="2" Margin="10,0,0,0">
            <!--TextBloky nad kalendářem-->
            <StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=lbOsoby, Path=SelectedItem}">
                <TextBlock Text="Narozeniny: " />
                <TextBlock Text="{Binding Narozeniny, StringFormat=dd.MM.yyyy}" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Vek: " />
                <TextBlock Text="{Binding Vek}" />
            </StackPanel>

            <!--Kalendář-->
            <Calendar x:Name="kalendar" SelectedDate="{Binding Narozeniny, Mode=OneTime}"
                      DisplayDate="{Binding Narozeniny, Mode=OneTime}"/>
        </StackPanel>

        <!--Spodní tlačítka-->
        <StackPanel Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Orientation="Horizontal"
                    HorizontalAlignment="Center">
            <Button x:Name="btnPridat" Content="_Přidat" Width="100" Margin="20 2" Click="btnPridat_Click" />
            <Button x:Name="btnOdebrat" Content="_Odebrat" Width="100" Margin="20 2" Click="btnOdebrat_Click" />
        </StackPanel>
    </Grid>
</Window>

Ale na to snad přijdu.

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

namespace WpfUpominacnarozenin
{
    public class SpravceOsob
    {
        public ObservableCollection<Osoba> Osoby { get; set; }

        public SpravceOsob()
        {
            Osoby = new ObservableCollection<Osoba>();
        }

        public Osoba NejblizsiOsoba { get; set; }

        public DateTime DnesniDatum
        {
            get
            {
                return DateTime.Now;
            }
        }

        private void NajdiNejblizsi()
        {
            var serazeneOsoby = Osoby.OrderBy(o => o.ZbyvaDni);
            if (serazeneOsoby.Count() > 0)
                NejblizsiOsoba = serazeneOsoby.First();
            else
                NejblizsiOsoba = null;
        }

        public void Pridej(string jmeno, DateTime? datumNarozeni)
        {
            if (jmeno.Length < 3)
                throw new ArgumentException("Jméno je příliš krátké");
            if (datumNarozeni == null)
                throw new ArgumentException("Nebylo zadané datum narození");
            if (datumNarozeni.Value.Date > DateTime.Today)
                throw new ArgumentException("Datum narození nesmí být v budoucnosti");
            Osoba osoba = new Osoba(jmeno, datumNarozeni.Value.Date);
            Osoby.Add(osoba);
            NajdiNejblizsi();
        }

        public void Odeber(Osoba osoba)
        {
            Osoby.Remove(osoba);
            NajdiNejblizsi();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfUpominacnarozenin
{
    public class Osoba
    {
        public string Jmeno { get; set; }
        public DateTime Narozeniny { get; set; }

        public int Vek
        {
            get
            {
                DateTime dnes = DateTime.Today;
                int vek = dnes.Year - Narozeniny.Year;
                if (dnes < Narozeniny.AddYears(vek))
                    vek--;
                return vek;
            }
        }

        public int ZbyvaDni
        {
            get
            {
                DateTime dnes = DateTime.Today;
                DateTime dalsiNarozeniny = Narozeniny.AddYears(Vek + 1);

                TimeSpan rozdil = dalsiNarozeniny - DateTime.Today;

                return Convert.ToInt32(rozdil.TotalDays);
            }
        }

        public override string ToString()
        {
            return Jmeno;
        }

        public Osoba(string jmeno, DateTime narozeniny)
        {
            Jmeno = jmeno;
            Narozeniny = narozeniny;
        }
    }
}
 
Nahoru Odpovědět
25.3.2018 21:04
Avatar
Jirka
Člen
Avatar
Odpovídá na Poggy
Jirka:25.3.2018 21:22

Ahoj, tohle by ti mohlo ohledně Bindingu pomoci:
Návod č.1
Návod č.2

 
Nahoru Odpovědět
25.3.2018 21:22
Avatar
Odpovídá na Poggy
Michal Štěpánek:25.3.2018 21:59

Chybu máš v tom, že nemáš "nabindovaný" ten StackPanel

<StackPanel Grid.Column="1" Grid.Row="2" Margin="10, 0, 0, 0" DataContext="{Binding ElementName=osobyListBox,Path=SelectedItem}">
Nahoru Odpovědět
25.3.2018 21:59
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Poggy
Člen
Avatar
Poggy:27.3.2018 18:33

Děkuji všem za reakce. Už semi povedlo vybojovat základy bindigu asnad už to dotáhnu.

Michal: Děkuji, já to napsal do špatného StackPanelu. Moje chyba.

 
Nahoru Odpovědět
27.3.2018 18:33
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 11 zpráv z 11.