WPF Problem z bindowniem

0

Witam, mam poblem aplikacja nie chce się odpalić i wywala błąd:

System.NullReferenceException occurred
Message=Odwołanie do obiektu nie zostało ustawione na wystąpienie obiektu.

Poradzcie coś bo już próbuję to rozkminić 4h :/

private ICommand _dodajKwoteCommand;

        public ICommand DodajKwote
        {
            get
            {
                if (_dodajKwoteCommand == null)
                    _dodajKwoteCommand = new RelayCommand(
                        (object argument) =>
                        {
                            decimal kwota = decimal.Parse(argument.ToString());
                            model.Dodaj(kwota);
                            OnPropertyChanged("Suma");
                        }
                        ,
                        (object argument) =>
                        {
                            decimal kwota = decimal.Parse(argument.ToString()); // tu wywala błąd
                            return CzyLancuchKwotyJestPoprawny(kwota);
                        }
                        );
                return _dodajKwoteCommand;
            }
        }
<Window x:Class="AsystentZakupow.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:AsystentZakupow"
        xmlns:mw="clr-namespace:AsystentZakupow.ViewModel"
        mc:Ignorable="d"
        Title="Asystent Zakupow" Height="200" Width="200">
    <Window.DataContext>
        <mw:ModelWidoku />
    </Window.DataContext>
    <Window.Resources>
        <local:Converter x:Key="boolToBrush" />
        <local:ConverterStringToDoublew x:Key="stringToDecimal" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <TextBlock HorizontalAlignment="Left" Grid.Row="0" FontSize="25" 
                   Foreground="Navy" Margin="10"
                   VerticalAlignment="Top">
            Suma: 
            <Run Foreground="Black" FontFamily="Courier New" Text="{Binding Suma, Mode=OneWay}"/>
        </TextBlock>
        <TextBox x:Name="tbKwota" Margin="10" Grid.Row="1" TextAlignment="Right" 
                 Foreground="{Binding ElementName=btnDodaj, Path=IsEnabled, Mode=OneWay, Converter={StaticResource boolToBrush}}"
                 FontFamily="Courier New" Text="0" FontSize="30"/>
        <Button x:Name="btnDodaj" 
                Margin="10" Grid.Row="2" 
                Command="{Binding DodajKwote}"
                CommandParameter="{Binding ElementName=tbKwota, Path=Text, Converter={StaticResource stringToDecimal}}"
                Content="Dodaj" FontSize="20" />
    </Grid>
</Window>

0

Kiedyś robiłem ten program.

Zamień sobie to:

decimal kwota = decimal.Parse(argument.ToString()); // tu wywala błąd
return CzyLancuchKwotyJestPoprawny(kwota);

na to:

return CzyLancuchKwotyJestPoprawny((string)argument);

i powinno być ok.

CzyLanuchKwotyJestPoprawny u mnie wyglądal tak:

public bool CzyLancuchKwotyJestPoprawny(string s)
        {
            if (string.IsNullOrWhiteSpace(s))
            {
                return false;
            }

            decimal kwota;

            if (!decimal.TryParse(s, out kwota))
            {
                return false;
            }
            else
            {
                return model.CzyKwotaJestPoprawna(kwota);
            }
        }
0

Tak już próbowałem i to samo. A powiedz mi czy w XAMLu masz tak samo? Bo właśnie wydaję mi się że to właśnie tam jest przyczyna błędu.

0

A jak wygląda u Ciebie metoda "CzyLanuchKwotyJestPoprawny" ?

1

A po co Ty w ogóle przekazujesz kontrolkę jako parametr przycisku zamiast zwyczajnie zbindować stringa do TextBox.Text? Zupełnie niepotrzebnie komplikujesz sobie kod:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows.Input;

namespace _4programmersWPF
{
    public class MainWindowVM : INotifyPropertyChanged
    {
        private string text;
        public string Text
        {
            get { return this.text; }
            set
            {
                this.text = value;
                this.OnPropertyChanged(nameof(this.Text));
            }
        }

        private decimal salary;

        public ICommand PressMe
        {
            get
            {
                return new RelayCommand(() =>
                {
                    //  Some code...
                },

                () => decimal.TryParse(this.Text, out this.salary));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string property)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}
<Window x:Class="_4programmersWPF.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:_4programmersWPF"
        xmlns:vm="clr-namespace:_4programmersWPF"
        mc:Ignorable="d"
        Title="4prog test app"
        Name="Window" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight">
  <Window.DataContext>
    <vm:MainWindowVM/>
  </Window.DataContext>
  <StackPanel>
    <TextBox Text="{Binding Text,UpdateSourceTrigger=PropertyChanged}"
             FontSize="14" Margin="5"/>
    <Button Command="{Binding PressMe}"
            Content="Press me!" FontSize="14" Margin="5"/>
  </StackPanel>
</Window>

Możesz pójść nawet o krok dalej i zbindować do textboksa własność typu decimal. Walidacja nastąpi automatycznie; pole zaświeci się na czerwono kiedy wpiszesz nieprawidłową wartość.

0

W zadaniu w pewnej książce było, żeby zrobic konwerter string na decimal a to co ty podałeś to jest całkiem inna ścieżka. Mógłbyś coś poradzić jak to zrobić na konwerterze?

1

Ale uwierz mi, że naprawdę nie potrzebujesz tam żadnego konwertera. Interfejsu IValueConverter używa się takich przypadkach jak ten tutaj: http://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/

U Ciebie wystarczy zwykłe bindowanie.

No ale jak już to...:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace _4programmersWPF
{
    public class Converter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var result = decimal.Zero;
            decimal.TryParse((value as string), out result);
            return result;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return string.Format("{0}", value != null ? (decimal)value : 0);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows.Input;

namespace _4programmersWPF
{
    public class MainWindowVM : INotifyPropertyChanged
    {
        private decimal salary;
        public decimal Salary
        {
            get { return this.salary; }
            set
            {
                this.salary = value;
                this.OnPropertyChanged(nameof(this.Salary));
            }
        }

        private string message;
        public string Message
        {
            get { return this.message; }
            set
            {
                this.message = value;
                this.OnPropertyChanged(nameof(this.Message));
            }
        }

        public ICommand PressMe
        {
            get
            {
                return new RelayCommandParametrized(p =>
                {
                    this.Salary = (decimal)p; // Przy konwerterze masz pewność, że tutaj nie sypnie Ci wyjątkiem.
                    this.Message = string.Format("{0}", this.Salary);
                });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string property)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}
<Window x:Class="_4programmersWPF.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:_4programmersWPF"
        xmlns:vm="clr-namespace:_4programmersWPF"
        mc:Ignorable="d"
        Title="4prog test app"
        Name="Window" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight">
  <Window.DataContext>
    <vm:MainWindowVM/>
  </Window.DataContext>
  <Window.Resources>
    <vm:Converter x:Key="Converter"/>
  </Window.Resources>
  <StackPanel>
    <TextBox Margin="5" Name="tbx"/>
    <Button Content="Press me!" Command="{Binding PressMe}"
            CommandParameter="{Binding ElementName=tbx,
            Path=Text,Converter={StaticResource Converter}}"
            Margin="5"/>
    <Label Content="{Binding Message, UpdateSourceTrigger=PropertyChanged}"
           Margin="5"/>
  </StackPanel>
</Window>

...i masz teraz taką mini walidację danych. Problem w tym, że stosowaniem konwerterów do takich rzeczy trochę zaciemniasz sobie kod, bo zwyczajnie można to po prostu zbindować.

0

Dzięki za wytłumaczenie problemu. Nie wiem czemu w książce uczą żeby zrobić konwerter string na double jak takiej praktyki się nie stosuje. Teraz już przynajmniej wiem że tak się nie robi. ;)

0

Miałbym jeszcze jeden problem, co prawda inny projekt ale myślę że mogę go napisać w tym temacie. Mam coś takiego
title

I teraz jeżeli kliknę na pozycję z listy to mam bindowanie do textboxów a wykonuję to poprzez selectValue np dla osoby:

uzupełnienie listy oraz binodwanie SelectedValue
<ListView x:Name="lvPersonsList" SelectedValue="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged}" 
                              ItemsSource="{Binding Path=PersonsList}" Grid.Row="1">

bindowanie do text boxów danych z SelectedValue
<Label Content="Imie "/>
                <TextBox x:Name="txbNameEdit" Text="{Binding SelectedPerson.PersonID.Name}" VerticalContentAlignment="Center" Height="40" />
                <Label Content="Nazwisko "/>
                <TextBox x:Name="txbSureNameEdit" Text="{Binding SelectedPerson.PersonID.SureName}" VerticalContentAlignment="Center" Height="40" />

Jak zrobić by po zmianie tekstu w okienku textboxa był on przypisywany do jakiegoś property żeby się go dało później go dalej przetworzyć (np wysłać update do bazy danych) ?

0

Prawdopodobnie wystarczy ci binding w trybie TwoWay:

<TextBox x:Name="txbNameEdit" Text="{Binding SelectedPerson.PersonID.Name, Mode=TwoWay}" VerticalContentAlignment="Center" Height="40" />

Teraz automatycznie Name zostanie zaktualizowane kiedy TextBox z nim związany straci focus.

0

No dobra, ale teraz tak robię to na obiektowej bazie danych i żeby wyszukać stare dane osobowe muszę mu wpisać stare wartości a tu mi podmieni stare na te zmienione z textboxa. Jak zrobić żeby był jakiś tempName oraz tempSureName do których zostaną przypisane oryginalne wartości?

0

Próbowałem coś takiego:

 private Person _selectedPerson;

        public Person SelectedPerson
        {
            get { return _selectedPerson; }
            set
            {
                _selectedPerson = value;
                _selectedPerson1 = value;
                OnPropertyChanged(nameof(this.SelectedPerson));
            }
        }

        private Person_selectedPerson1;

        public Person SelectedPerson1
        {
            get { return _selectedPerson1; }
            set
            {
                _selectedPerson1 = value;
                OnPropertyChanged(nameof(this.SelectedPerson1));
            }
        }

i teraz mam inny problem, dlaczego gdy teraz w teztboxie zmieniam wartość dla SelectedPerson1.Name to zmienia się też automatycznie dla SelectedPerson.Name ?

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