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.
Related
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.
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
...
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?
The TextBlock displays the default property metadata, but doesn't update when the DependencyProperty changes.
It looks like the view isn't updating, because reading the dependency property after assigning a new value returns the new value.
Here's the code, thanks in advance:
Data.cs:
using System.Windows;
namespace RMS.Kernel
{
public class Data : DependencyObject
{
private delegate void ObjectDelegate(object obj);
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(Data), new PropertyMetadata("start text"));
private string message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public Data() { }
//singleton, make every method accesible anywhere. Only one gui object exists on runtime...
private static Data shared;
public static Data getShared()
{
if (shared == null)
{
shared = new Data();
}
return shared;
}
public void setProperties(string message)
{
this.message = message;
}
}
}
XAML:
<local:CustomWindow x:Class="RMS.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:RMS"
xmlns:control="clr-namespace:RMS.Controls"
xmlns:kernel="clr-namespace:RMS.Kernel"
Title="MainWindow" Height="750" Width="1200">
<Window.DataContext>
<kernel:Data/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<control:CustomToolBar x:Name="customToolBar" VerticalAlignment="Top" Grid.Row="0"/>
<TextBlock Text="{Binding Path=Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="White" FontSize="12pt" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1"/>
<control:CustomStatusBar x:Name="CustomStatusBar" VerticalAlignment="Bottom" Grid.Row="2"/>
</Grid>
</local:CustomWindow>
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; }
}