I'm very new in C#, WPF, XAML and Binding, and to learn this nice environment, I created this little exercise myself: I want to show a price including VAT and excluding VAT. But I want to have the other field be updated automatically as soon as I change a field (so as soon as I hit Tab).
I have already created a class (thanks to help in another question I posted in Stack Overflow) to do the math. But my issue us now that the fields don't get updated. I use the INotifyPropertyChange Interface, but for some reason, it just doesn't work.
Here's what is happening:
I use the following XAML code:
<Window x:Class="BTWv2.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:BTWv2"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="250">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Label Grid.Column="1" Grid.Row="1" Content="Amount inc VAT:" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox
Grid.Column="2" Grid.Row="1"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="100" Text="{Binding IncludeVat,Mode=TwoWay}"/>
<Label Grid.Column="1" Grid.Row="3" Content="Amount excl VAT:" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding ExcludeVat,Mode=TwoWay}"
Grid.Column="2" Grid.Row="3"
VerticalAlignment="Center" HorizontalAlignment="Center"
MinWidth="100"
/>
</Grid>
</Window>
And here is the actual C# code (I'm using a class named CalculateVAT for the calculation of the numbers:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace BTWv2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CalculateVAT Price = new CalculateVAT();
Price.IncludeVat = 100;
DataContext = Price;
}
public class CalculateVAT : INotifyPropertyChanged
{
private decimal m_IncludeVat; // <- decimal is a better choice for finance
// To compute VAT we should know the percent; let it be known
public const decimal Percent = 21.0m;
public decimal IncludeVat
{
get => m_IncludeVat;
set
{
// negative cash are usually invalid; if it's not the case, drop this check
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
m_IncludeVat = value;
Tax = Math.Round(m_IncludeVat / 100 * Percent, 2);
NotifyPropertyChanged();
}
}
public decimal ExcludeVat
{
get => m_IncludeVat - Tax;
set
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
m_IncludeVat = Math.Round(value / 100 * (100 + Percent), 2);
Tax = m_IncludeVat - value;
NotifyPropertyChanged();
}
}
// Let's be nice and provide Tax value as well as IncludeVat, ExcludeVat
public decimal Tax { get; private set; }
public override string ToString() =>
$"Include: {IncludeVat:f2}; exclude: {ExcludeVat:f2} (tax: {Tax:f2})";
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Has anyone an idea what I'm missing here?
I already tried the UpdateSourceTrigger=PropertyChanged setting in the binding, but that doesn't seem to change anything..
Let's say you want to update ExcludeVat when you change IncludeVat from UI.
You will need to raise notify property changed for both the properties in setter of IncludeVat.
public decimal IncludeVat
{
get => m_IncludeVat;
set
{
// negative cash are usually invalid; if it's not the case, drop this check
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
m_IncludeVat = value;
Tax = Math.Round(m_IncludeVat / 100 * Percent, 2);
NotifyPropertyChanged(nameof(IncludeVat));
NotifyPropertyChanged(nameof(ExcludeVat));
}
}
What this will do is , when you change IncludeVat from UI , the setter now raises property changed for ExcludeVat. UI will try to update by calling the getter of ExcludeVat. You getter for ExcludeVat : get => m_IncludeVat - Tax; will now get the updated value and UI will reflect that.
Related
I have a texblock in my .xaml file that I have binded to a property. There are also 2 buttons to ++ and -- the value of that property when they are clicked. The problem is that the binding works only at the start of the application.
When the button is clicked the corresponding command is executed and variable changes its value in the code (so there is no problem with executing the given methods).
However no changes are visible in the UI. I am not sure what might be the cause here, is the UI being somehow blocked? I have always worked using MVVM way and never have had a problem like that with binding.
If more code is needed let me know, didn't want to put a huge wall of code here.
Constructor:
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Zoom = 60;
ZoomPlusSceneCommand = new RelayCommand(ZoomPlus);
ZoomMinusSceneCommand = new RelayCommand(ZoomMinus);
}
Handling property changed:
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Property itself:
private int zoom;
public int Zoom
{
get { return zoom; }
set { if (zoom != value) { zoom = value; RaisePropertyChanged("Zoom"); } }
}
Commands:
public RelayCommand ZoomPlusSceneCommand { get; set; }
public RelayCommand ZoomMinusSceneCommand { get; set; }
where RelayCommand is an implementation that I use as a template in most of my projects and they've always been working well (at least in MVVM projects)
Methods executed with the usage of commands when a button is clicked:
private void ZoomPlus(object o)
{
Zoom++;
}
private void ZoomMinus(object o)
{
Zoom--;
}
Xaml:
<Window x:Class="Rendering3D.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:Rendering3D"
Loaded="GetSceneSize"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="900">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition x:Name="SceneColumn" Width="*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition x:Name="SceneRow" Height="*"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Image x:Name="SceneImage" Grid.Column="1" Grid.Row="1"
PreviewMouseLeftButtonDown="SceneMouseLeftButtonDown"
MouseMove="SceneMouseMove"
MouseWheel="SceneMouseWheel"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="0 5 0 5"/>
</Style>
</Grid.Resources>
<Button Grid.Row="0" Content="+" Command="{Binding ZoomPlusSceneCommand}"/>
<Button Grid.Row="2" Content="-" Command="{Binding ZoomMinusSceneCommand}"/>
<Label Grid.Row="4" Content="Zoom" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="5" Text="{Binding Zoom}" HorizontalAlignment="Center"/>
</Grid>
</Grid>
Example of a mouse event:
private void SceneMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
Zoom++;
else
Zoom--;
return;
}
Assuming the property gets set, the view should be updated provided that your MainWindow class actually implements INotifyPropertyChanged:
public partial class MainWindow : Window, INotifyPropertyChanged
...
I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;
Question: ObservableCollection not updated to UI when property changed
What I have tried:
XAML view:
<UserControl x:Class="FlexilineDotNetGui.Flexiline.UserControls.UCRealestate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:translations="clr-namespace:FlexilineDotNetGui.Flexiline.Translations"
xmlns:viewModels="clr-namespace:FlexilineDotNetGui.Flexiline.ViewModels"
x:Name="Realestate"
DataContext="{StaticResource vmRealestate}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Height="200" Margin="5"
ItemsSource="{Binding Panden, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False"
CanUserAddRows="False" IsReadOnly="True" SelectedItem="{Binding Pand}">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static translations:UCRealestate.RegistrationType}" Binding="{Binding RegistrationType, Mode=TwoWay}" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Column="1" Grid.Row="0" Background="Transparent"
Width="20" Height="20" Padding="0" Margin="5" Command="{Binding AddPandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/New_16x16.ico"/>
</Button>
<Button Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"
Width="20" Height="20" Padding="0" Background="Transparent" Margin="5" Command="{Binding DeletePandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/Delete_16x16.ico"/>
</Button>
<Grid Grid.Row="3" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="100"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<Label Grid.Row="2" Grid.Column="2" Content="{x:Static translations:UCRealestate.RegistrationType}" HorizontalContentAlignment="Right" VerticalAlignment="Center"/>
<ComboBox Grid.Row="2" Grid.Column="3" Margin="5" ItemsSource="{Binding AardInschrijving, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedAardInschrijving, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Description" SelectedValuePath="FlexKey"/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</UserControl>
ViewModel :
using FlexilineDotNet.SharedDomainLogic.Models.CommunicationModels;
using FlexilineDotNetGui.Domain.Controls;
using FlexilineDotNetGui.Domain.Models;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace FlexilineDotNetGui.Flexiline.ViewModels
{
public class RealestateViewModel : ANavigationPaneSubViewModel, INotifyPropertyChanged
{
BorgRealestate _borgRealestate = new BorgRealestate();
ObservableCollection<BorgRealestate> ocPanden = new ObservableCollection<BorgRealestate>();
private DomainController _controller = DomainController.GetInstance();
public RelayCommand<object> AddPandCMD { get; private set; }
public RelayCommand<object> DeletePandCMD { get; private set; }
#region "properties"
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
//public ObservableCollection<BorgRealestate> Panden { get; }
public BorgRealestate Pand
{
get
{
return _borgRealestate ?? (_borgRealestate = new BorgRealestate());
}
set
{
if (value == _borgRealestate) return;
_borgRealestate = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public List<DropDownItem> AardInschrijving { get { return _controller.GetDropDownItemsByType("AardInschrijving"); } }
private DropDownItem SelectedAardInschrijvingDI
{
get
{
DropDownItem test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault(x => x.FlexKey == _borgRealestate?.RegistrationType);
if (test == null)
{
test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault();
}
return test;
}
set
{
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public int SelectedAardInschrijving
{
get
{
return SelectedAardInschrijvingDI.FlexKey;
}
set
{
if (value == Pand?.RegistrationType) return;
Pand.RegistrationType = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public string SelectedAardInschrijvingText
{
get
{
return SelectedAardInschrijvingDI.Description;
}
}
#endregion
#region "ctor"
public RealestateViewModel()
{
//Panden = new ObservableCollection<BorgRealestate>();
AddPandCMD = new RelayCommand<object>(o => AddPand());
DeletePandCMD = new RelayCommand<object>(o => DeletePand());
}
#endregion
#region "methods"
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
private void DeletePand()
{
Panden.Remove(Pand);
}
#endregion
}
}
Problem description:
When I Update the combobox value, it's updated in the "Panden" property when I check with a breakpoint but the datagrid in the view is not updated. The problem exists with Oneway and also with TwoWay as I defined in datagrid columns. I have both modes tried.
The views are inside a dxnavbar control, when I switch between navbar items and back then the view is updated OK.
EDIT:
When the application starts, the Panden list is indeed null, I had forgotten to mention something....
In my view there is a datagrid with a button where I add items to the observable collection where also some of the properies as example nature inscription combobox value is shown in datagrid. This button is then connected to a relay command. That is the collection that i have binded to the datagridview.
the getter of the property property is not empty and is therefore filled with the correct values, only the changes do not change in the view.
it is indeed normal that the collection is null if no "Pand" items are added to the "Panden" collection via the provided button on the UI (relaycommand). But the "Pand" item in de datagrid will not update after i change a value from the combobox after adding an item with the button to the collection.
EDIT 21/11 08:54
This is the logic for adding the pand:
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
But after i add i see the items gets added to the collection and get also updated in the collection after the combobox value changed only not in ui. (when the row in the datagrid have the focus)
In this code here:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
You create a new collection but you don't at that point call OnPropertyChanged; and you shouldn't because it would be wrong to do this here. Therefore, the UI doesn't know you've created the new collection. It looks like the first time the above getter is called is in your AddPand function. Therefore, the UI never receives an OnPropertyChanged for Panden and so does not update.
Instead of this create the collection in your constructor and you should find it will update. Also you may not need a setter at all for Panden if it is never recreated.
in constructor:
Panden = new ObservableCollection<BorgRealestate>();
Then Panden property becomes:
public ObservableCollection<BorgRealestate> Panden { get; }
Which can be simplified to:
public ObservableCollection<BorgRealestate> Panden { get; } = new ObservableCollection<BorgRealestate>();
In the Viewmodel:
That short piece of code did the trick:
CollectionViewSource.GetDefaultView(ocPanden).Refresh();
Replace this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
With this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
if(ocPanden != null)
{
CollectionViewSource.GetDefaultView(ocPanden).Refresh(); //This will do the trick
}
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
Unfortunately I have not yet found out where the cause really comes from?
If someone can know that then leave a message?
I have a Slider in WPF and I want to bind the same slider to two different Values. But each of these values have different range. For example the first one represents time and changes between 0 to 1.5 second and the second value represent percentage that ranges from 0 to 100. Is it possible to bind the value of the Slider to both of them in a way that user can also type any value within the range and both slider and other value get updated. For example if the user put the time value to 1, the Slider should move and also the value of the percentage should be set to 66.66 %.
Thanks in advance
Your solution might be something like this:
MainWindow.xaml
<Window x:Class="WpfApp1.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:wpfApp1="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance d:Type=wpfApp1:SampleViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="0-1.5" Grid.Column="0" Grid.Row="0"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Value1, Mode=TwoWay}"/>
<TextBlock Text="0-100" Grid.Column="0" Grid.Row="1"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Value2, Mode=TwoWay}"/>
<Slider Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" Height="25" Minimum="0" Maximum="100" Value="{Binding Value2}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new SampleViewModel();
}
}
public sealed class SampleViewModel : INotifyPropertyChanged
{
private double _value1;
private double _value2;
public event PropertyChangedEventHandler PropertyChanged;
public double Value1
{
get { return _value1; }
set
{
if (_value1 == value)
return;
_value1 = value;
OnPropertyChanged(nameof(Value1));
_value2 = Math.Round(100 / 1.5 * value, 1);
OnPropertyChanged(nameof(Value2));
}
}
public double Value2
{
get { return _value2; }
set
{
if (_value2 == value)
return;
_value2 = value;
OnPropertyChanged(nameof(Value2));
_value1 = Math.Round(1.5 /100* value, 1);
OnPropertyChanged(nameof(Value1));
}
}
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
However its for a 'well-known' min/max values. If you need dynamic ones (you dont know min/max on compile time, and it would be the only right solution to avoid any hardcoded stuff) you need to do some math.
Also you can achieve the same result using multibinding, just pass min/max and values and do all the math inside.
I'm dynamically create two textboxes and a textblock. The user first clicks a button which adds a row of controls and then inputs numbers in each textbox. The sum of the two boxes for a given row will be displayed in a text block.
Here is the XAML.
<Window x:Class="ModelBuilder_080614.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModelBuilder_080614"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<!-- this is a comment -->
<local:MainWindowViewModel />
</Window.DataContext>
<Canvas>
<Button Canvas.Top="21" Canvas.Left="20" Content="Add TextBox" Command="{Binding TestCommand}"/>
<ItemsControl Canvas.Top="50" Canvas.Left="50" ItemsSource="{Binding SomeCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Text="{Binding Path=.}"/>
<TextBox Grid.Column="1" Grid.Row="0" Name="Bench" Text="{Binding Path=.}"/>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding <!-- I'm LOST -->}"/>
<!-- I want this TextBlock to sum the Two TextBlocks -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Window>
And here is my Model and ViewModel in C#.
using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Data;
using MicroMvvm;
namespace ModelBuilder_080614
{
public class MainWindowViewModel
{
public ObservableCollection<Model> SomeCollection { get; set; }
public ICommand TestCommand { get; private set; }
public MainWindowViewModel()
{
SomeCollection = new ObservableCollection<Model>();
TestCommand = new RelayCommand<object>(CommandMethod);
}
private void CommandMethod(object parameter)
{
SomeCollection.Add(new Model());
}
}
public class Model : INotifyPropertyChanged
{
double _actual;
double _bench;
double _active;
public double Actual
{
get { return _actual; }
set { _actual = value; }
}
public double Bench
{
get { return _bench; }
set { _bench = value; }
}
public double Active
{
get { return _active; }
set { _active = Actual - Bench; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
How do you bind the contents of the textboxes to display the sum of them in the TextBlock?
Bind to the properties in your ViewModel: (I removed the grid settings to make the example clearer)
<TextBox Text="{Binding Path=Actual, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Path=Bench, UpdateSourceTrigger=PropertyChanged" />
<TextBlock Text="{Binding Path=Active" />
Then when your values change, raise the notification on the "Active" property so it updates. You only need a "getter" on the "Active" property... when you call OnPropertyChanged(), it will perform the calculation and update the field for you.
double _actual;
double _bench;
public double Actual
{
get { return _actual; }
set
{
_actual = value;
OnPropertyChanged("Active");
}
}
public double Bench
{
get { return _bench; }
set
{
_bench = value;
OnPropertyChanged("Active");
}
}
public double Active
{
get { return Actual - Bench; }
}