I am trying to make a field in ListView editable.
I mean when I select row in listView one field becomes textbox instead of textblock.
But it doesnt work - I see both controls all the time.
xaml:
<Window x:Class="fastnn_speedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:fastnn_speedTest"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="fastnn: not saved" Height="300" Width="300">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="VisibilityOfBool" />
<local:StringToIntValueConverter x:Key="StringToInt" />
<local:StringToFloatValueConverter x:Key="StringToFloat" />
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="ActivationEnum">
<ObjectDataProvider.MethodParameters>
<x:Type x:TypeName="local:Network+Layer+ActivFunction" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="ComputeMethodEnum">
<ObjectDataProvider.MethodParameters>
<x:Type x:TypeName="local:Training+ComputeMethodEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<Style TargetType="{x:Type TextBlock}" x:Key="ListTextBlockStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource VisibilityOfBool}, ConverterParameter=True}" />
</Style>
<Style TargetType="{x:Type FrameworkElement}" x:Key="ListTextBoxStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource VisibilityOfBool}, ConverterParameter=False}" />
</Style>
<DataTemplate x:Key="ActivationTemplate">
<ComboBox ItemsSource="{Binding Source={StaticResource ActivationEnum}}" SelectedValue="{Binding Path=Activation}" Width="100"/>
</DataTemplate>
<DataTemplate x:Key="NeuronsTemplate">
<Grid>
<TextBox Text="{Binding Path=Neurons}" Width="40" Style="{Binding Source=StaticResource ListTextBoxStyle }"></TextBox>
<TextBlock Text="{Binding Path=Neurons}" Width="40" Style="{Binding Source=StaticResource ListTextBlockStyle }"></TextBlock>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="New" Click="MenuNew_Click"></MenuItem>
<MenuItem Header="Open" Click="MenuOpen_Click"></MenuItem>
<Separator></Separator>
<MenuItem Header="Save" Click="MenuSave_Click"></MenuItem>
<MenuItem Header="Save As" Click="MenuSaveAs_Click"></MenuItem>
<Separator></Separator>
<MenuItem Header="Exit"></MenuItem>
</MenuItem>
</Menu>
<TabControl Grid.Row="1" Name="Tabs">
<TabItem Header="Network">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<ListView Grid.Row="0" x:Name="NetworkListview" ItemsSource="{Binding network.Layers}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="layer name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="60" Header="neurons" CellTemplate="{StaticResource NeuronsTemplate}"/>
<GridViewColumn Width="110" Header="activation" CellTemplate="{StaticResource ActivationTemplate}"/>
</GridView>
</ListView.View>
</ListView>
<UniformGrid Grid.Row="1" Rows="1">
<Button Name="AddLayerButton" Click="AddLayerButton_Click">Add layer</Button>
<Button Name="RemoveLayerButton" Click="RemoveLayerButton_Click">Remove layer</Button>
</UniformGrid>
</Grid>
</TabItem>
<TabItem Header="Data set">
<UniformGrid Columns="2" Height="70" Width="220">
<Label>input dimmension</Label>
<TextBox Text="{Binding Path=data_set.InputDimmension, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>output dimmension</Label>
<TextBox Text="{Binding Path=data_set.OutputDimmension, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>number of samples</Label>
<TextBox Text="{Binding Path=data_set.SamplesNumber, Mode=TwoWay}"></TextBox>
</UniformGrid>
</TabItem>
<TabItem Header="Training">
<UniformGrid Columns="2" Width="230" Height="170">
<Label>learning rate</Label>
<TextBox Text="{Binding Path=training.LearningRate, Mode=TwoWay, Converter={StaticResource StringToFloat}}"></TextBox>
<Label>epochs</Label>
<TextBox Text="{Binding Path=training.Epochs, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>weights stddev</Label>
<TextBox Text="{Binding Path=training.WeightsStddev, Mode=TwoWay, Converter={StaticResource StringToFloat}}"></TextBox>
<Label>srand(0)</Label>
<CheckBox Margin="0,4,0,0" IsChecked="{Binding Path=training.SrandZero}"></CheckBox>
<Label>compute method</Label>
<ComboBox Name="ComputeMethodCombo" ItemsSource="{Binding Source={StaticResource ComputeMethodEnum}}" SelectedValue="{Binding Path=training.ComputeMethod}"></ComboBox>
<Label>select device</Label>
<ComboBox Name="DeviceComboBox" SelectedIndex="{Binding Path=training.DeviceIndex}"></ComboBox>
<Label>click to run test</Label>
<Button Margin="2" Name="RunTestButton" Click="RunTestButton_Click">Run test</Button>
</UniformGrid>
</TabItem>
<TabItem Header="Output">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Name="LogTextBox"></TextBox>
<UniformGrid Grid.Row="1" Rows="1">
<Button>Save</Button>
<Button Name="ClearButton" Click="ClearButton_Click">Clear</Button>
<Button>Eerror chart</Button>
<Button>Speed chart</Button>
</UniformGrid>
</Grid>
</TabItem>
</TabControl>
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock>text</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
converter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and a Network class which contains ObservableCollection which is bound to listview:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Xml.Serialization;
namespace fastnn_speedTest
{
public class Network
{
public class Layer : INotifyPropertyChanged
{
public enum ActivFunction { LINEAR, EXPONENTIAL, ARCUSTANGENT }
private string name;
[XmlIgnore]
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
[XmlAttribute]
public ActivFunction Activation { get; set; }
[XmlAttribute]
public int Neurons { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public ObservableCollection<Layer> Layers { get; set; }
public Network()
{
Layers = new ObservableCollection<Layer>();
}
public void AddLayer(Layer layer)
{
int last = Layers.Count;
if (last > 0)
{
Layers.Last().Name = "Layer " + last + " (hidden)";
}
layer.Name = "Layer " + (last + 1) + " (output)";
Layers.Add(layer);
}
public void RemoveLayer(int index)
{
if( index >= 0 && index < Layers.Count )
Layers.RemoveAt(index);
}
public void Clear()
{
Layers.Clear();
}
public void Validate()
{
if (Layers.Count < 1)
throw new ArgumentException("minimum number of layers is 1");
foreach (Layer layer in Layers)
{
if (layer.Neurons <= 0)
throw new ArgumentException("neurons in each layer must be > 0");
}
if(Layers.Last().Activation != Layer.ActivFunction.LINEAR)
throw new ArgumentException("last layer must be linear");
}
}
}
Have a look at the below links
http://www.switchonthecode.com/tutorials/wpf-tutorial-using-the-listview-part-3-in-place-edit
EDIT
You may have followed the tut but couple of things you missed is what causing the probs.First of all you are trying to use the inbuilt BooleanToVisibility converter but i think in your scenario its not gonna help.You have to crate a custom value converter like mentioned in the tutorial.
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Also you have to mention your converter like this
<Setter Property="Visibility" Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource VisibilityOfBool}, ConverterParameter=False}" />
Make these changes and you are good to go....
Why don't you use the data grid? You can style it to look like a list and it already provides the edit mode functionality. You can use a TemplateColumn to provide custom display and editing views.
Related
I am having troubles grouping RadioButtons when using a WPF form and using DataBinding.
If I manually create my controls the behavior is as expected and the user is only allowed to select one item from the radiobutton group (bottom GroupBox), but if I use nested ItemControls to generate my layout and use data binding grouping does not work as expected and a user is allowed to select multiple radiobuttons in one group (top groupBox) as can be seen in this linked screenshot 1.
I have tried binding the GroupName property but fail to somehow find a binding property to use for GroupName property.
Perhaps the Text property of the TextBlock in the same grid row can be used, but I fail to find the proper Binding syntax to reference it.
What would be the way to get grouping to work?
Here is the XAML:
<Window x:Class="RadioButtonMadness.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"
mc:Ignorable="d" Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="RadioButton">
<Setter Property="Margin" Value="10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="22" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="10" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="22" />
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions />
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Prognostic factors - NOK">
<ItemsControl ItemsSource="{Binding PrognosticFactors}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="PrognosticFactors" Grid.Row="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<TextBlock Text="{Binding Title}" />
<ItemsControl ItemsSource="{Binding Options}" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Key}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<GroupBox Header="Prognostic factors - OK" Grid.Row="1">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions/>
<TextBlock Text="Test4" Grid.Column="0"/>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<RadioButton Content="One"/>
<RadioButton Content="Two"/>
<RadioButton Content="Three"/>
</StackPanel>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions />
<TextBlock Text="Test5" Grid.Column="0"/>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<RadioButton Content="One"/>
<RadioButton Content="Two"/>
<RadioButton Content="Three"/>
</StackPanel>
</Grid>
</StackPanel>
</GroupBox>
</Grid>
</Window>
As well as the code behind:
using System;
using System.Collections.Generic;
using System.Windows;
namespace RadioButtonMadness
{
public class Option
{
public String Key{ get; set; }
public Double Value { get; set; }
public Option(String key, Double value)
{
Key = key;
Value = value;
}
}
public class PrognosticFactor
{
private String title;
public List<Option> Options
{
get
{
var list = new List<Option>();
list.Add(new Option("One", 1));
list.Add(new Option("Two", 2));
list.Add(new Option("Three", 3));
return list;
}
}
public String Title
{
get { return title; }
}
public PrognosticFactor(String value)
{
title = value;
}
}
public class ViewModel
{
public List<PrognosticFactor> PrognosticFactors
{
get
{
var list = new List<PrognosticFactor>();
list.Add(new PrognosticFactor("Test1"));
list.Add(new PrognosticFactor("Test2"));
list.Add(new PrognosticFactor("Test3"));
return list;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
Just bind GroupName to the Title property. You can do it something like this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Key}" GroupName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Path=DataContext.Title}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
I have a ListView (with an inner ListView) that displays data like this:
I would like to display the inner ListView headers above the grouping like so:
Is it possible to re-position the column headers as shown or simply create some fake headers on the outer ListView?
Here is the XAML code I have so far:
<ListView Name="ListView_GarnishmentCalculations"
ItemsSource="{Binding GarnishedEmployees, UpdateSourceTrigger=PropertyChanged}"
MaxHeight="{Binding ActualHeight,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollContentPresenter}},
Converter={StaticResource MathConverter}, ConverterParameter=x-220}"
Margin="5,20,10,10"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4">
<!-- Required for right justifying text in a TextBlock -->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<!-- Group results and show EmpNo, Name and WorkState -->
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="175" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Items[0].EmpNo}"
FontWeight="Bold"
Grid.Column="0" />
<TextBlock Text="{Binding Items[0].FullName}"
FontWeight="Bold"
Grid.Column="1" />
<TextBlock Text="{Binding Items[0].WorkState}"
FontWeight="Bold"
Grid.Column="2" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<!-- Inner ListView of garnishment details -->
<ListView ItemsSource="{Binding Garnishments}">
<ListView.View>
<GridView>
<!-- CaseID -->
<GridViewColumn Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CaseNumber, Converter={StaticResource StringIsNullOrEmptyConverter}, ConverterParameter='No Case ID'}"
TextAlignment="Left">
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumn.Header>
<GridViewColumnHeader Content=" Case ID" />
</GridViewColumn.Header>
</GridViewColumn>
<!-- Vendor -->
<GridViewColumn Width="150"
DisplayMemberBinding="{Binding Vendor}">
<GridViewColumn.Header>
<GridViewColumnHeader Content=" Vendor" />
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is what I came up with, NOT by my self I used my google fu skills for this.
Credit to this SO post.
So here is what I have for my model:
namespace Model
{
public class Case
{
public int CaseID { get; set; }
public int Vendor { get; set; }
}
}
And now user:
namespace Model
{
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string State { get; set; }
public List<Case> Cases { get; set; }
}
}
Now in my MainViewModel:
using Model;
using System.Collections.Generic;
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
Users = new List<User>();
for (int i = 0; i < 20000; i++)
{
Users.Add(new User
{
ID = i,
Name = $"John the {i + 1}",
State = i % 2 == 0 ? "CA" : "IL",
Cases = new List<Case>() { new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 }, new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 } }
});
}
}
private List<User> users;
public List<User> Users
{
get { return users; }
set { users = value; OnPropertyChanged(); }
}
}
}
On to the View:
<Window x:Class="SO_App.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:vm="clr-namespace:VM;assembly=VM"
xmlns:model="clr-namespace:Model;assembly=Model"
xmlns:local="clr-namespace:SO_App"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource Source="{Binding Users}" x:Key="Users"/>
</Window.Resources>
<Grid>
<ListView>
<ListView.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource Users}}"/>
</CompositeCollection>
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridViewColumn Header="Case ID" Width="100"/>
<GridViewColumn Header="Vendor" Width="100"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ID}" MinWidth="50"/>
<TextBlock Text="{Binding Name}" MinWidth="250" Grid.Column="1"/>
<TextBlock Text="{Binding State}" MinWidth="50" Grid.Column="2"/>
<ListView Grid.Row="1" ItemsSource="{Binding Cases}" Grid.ColumnSpan="3">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding CaseID}" MinWidth="100"/>
<TextBlock Text="{Binding Vendor}" MinWidth="100" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Caveat:
You will need to handle the scroll event on the inner ListView so it doesn't swallow the mouse scroll.
P.S.
this is the BaseViewModel implementation:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace VM
{
public class BaseViewModel : INotifyPropertyChanged
{
#region INPC
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string prop = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
}
Which then produces this as a result:
I have an ItemsControl to display translations text fields.
I want to setup validating, so if all translations are empty, there was an error, and fields was marked as "error".
Is there any possibilty to do this?
My xaml:
<ItemsControl x:Name="LanguageItemsControl" ItemsSource="{Binding Path=Translations, Mode=TwoWay}"
LostFocus="OnLostFocus" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5,2,5,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="47*"/>
<ColumnDefinition Width="53*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="ItemLabel" VerticalAlignment="Center"
Text="{Binding Path=Key, StringFormat={x:Static res:Resources.lblCaption}}" />
<TextBox Grid.Column="1" x:Name="ItemText" VerticalAlignment="Center"
HorizontalAlignment="Stretch" Margin="2,0,22,0"
Text="{Binding Path=Value, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"
LostFocus="OnLostFocus"
AcceptsReturn="True"
MaxLines="2"
ScrollViewer.VerticalScrollBarVisibility="Auto"
MaxLength="150">
</TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My model's class implements from IDataErrorInfo and INotifyPropertyChanged
Translations is an ObservableCollection of custom type "LanguageValue" with public properties Key and Value.
I had in my model string this[string columnName], which works perfect with simple text boxes (outside ItemsControl), but how can make this works with my items? I've thight something like:
public string this[string columnName]
{
get
{
string result = null;
...
if (columnName == "Translations" || columnName == "ItemText")
{
if (Translations.All(t => string.IsNullOrEmpty(t.Value)))
result = Properties.Resources.errMsgEnterName;
}
...
But of course this didn't work.
Any suggestions?
Sure, I'm giving you a full implementation but with just the "Value" property. Do the same with all other properties that you want to validate:
1.Translation Model with IDataErrorInfo interface implementation:
public class Translation : BindableBase, IDataErrorInfo
{
public string Value { get; set; }
public string this[string propertyName]
{
get
{
return GetErrorForPropery(propertyName);
}
}
public string Error { get; }
private string GetErrorForPropery(string propertyName)
{
switch (propertyName)
{
case "Value":
if (string.IsNullOrEmpty(Value))
{
return "Please enter value";
}
return string.Empty;
default:
return string.Empty;
}
}
}
2.Initialize Translations in your ViewModel:
public ObservableCollection<Translation> Translations { get; set; }
public MainViewModel()
{
Translations = new ObservableCollection<Translation>
{
new Translation {Value = "A"},
new Translation (),
new Translation {Value = "C"}
};
}
3.Xaml with ValidatesOnDataErrors on the Value TextBox:
<ItemsControl x:Name="LanguageItemsControl" ItemsSource="{Binding Path=Translations, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5,2,5,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="47*"/>
<ColumnDefinition Width="53*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" x:Name="ItemLabel" VerticalAlignment="Center" Text="{Binding Path=Value, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
4.That will show a red box around the empty TextBox, if you want to display the error messeage when hovering overthe TextBox you need a tool tip:
<Window x:Class="WpfApplication1.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:customControlLib="clr-namespace:CustomControlLib;assembly=CustomControlLib"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow"
DataContext="{StaticResource MainViewModel}">
<Window.Resources>
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ItemsControl x:Name="LanguageItemsControl" ItemsSource="{Binding Path=Translations, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5,2,5,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="47*"/>
<ColumnDefinition Width="53*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" x:Name="ItemLabel" Style="{StaticResource TextBoxStyle}" VerticalAlignment="Center" Text="{Binding Path=Value, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am building a WPF application using MVVM Light. In it I have a dialog box. The XAML:
<Window x:Class="ParserEditor.NewParserDialog"
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:ParserEditor"
xmlns:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding NewParser, Source={StaticResource Locator}}"
Title="New Parser..."
SizeToContent="Height"
Width="300">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
<BitmapImage x:Key="ErrorImage" UriSource="Resources/Error.png" />
<local:BooleanToVisibilityConverter x:Key="BoolToVisiblity" True="Visible" False="Collapsed" />
<ControlTemplate x:Key="InputErrorTemplate">
<DockPanel LastChildFill="True">
<Image DockPanel.Dock="Right"
Height="16"
Margin="5"
Source="{StaticResource ErrorImage}"
ToolTip="Contains invalid data"
VerticalAlignment="Center"
Width="16" />
<Border BorderBrush="Red"
BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
<Style TargetType="ComboBox">
<Setter Property="Margin" Value="5,4,26,4" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource InputErrorTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Name="PromptLabel"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Text="{Binding Path=Prompt, Mode=TwoWay}"
Visibility="{Binding Path=HasPrompt, Converter={StaticResource BoolToVisiblity}}"/>
<TextBlock Name="ParserTypeLabel"
Grid.Column="0"
Grid.Row="2"
Text="Parser Type:" />
<ComboBox Name="ParserTypePicker"
Grid.Column="1"
Grid.Row="2"
ItemsSource="{Binding Path=ParserTypes}"
SelectedItem="{Binding Path=ParserType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<Grid Name="ButtonGrid"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Name="OkButton"
Command="{Binding CloseCommand, ValidatesOnDataErrors=True}"
Grid.Column="0"
Content="OK"
IsDefault="True" />
<Button Name="CancelButton"
Grid.Column="1"
Content="Cancel"
IsCancel="True" />
</Grid>
</Grid>
</Window>
The View Model object:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using ParserEditor.Model;
namespace ParserEditor.ViewModel {
public class NewParserViewModel : ViewModelBase, IDataErrorInfo {
private readonly IDataService _dataService;
public string ParserType {
get { return _ParserType; }
set { Set( ref _ParserType, value ); }
}
private string _ParserType;
public ObservableCollection<string> ParserTypes { get; private set; }
public bool HasPrompt {
get { return !string.IsNullOrWhiteSpace( Prompt ); }
}
public string Prompt {
get { return _Prompt; }
set {
Set( ref _Prompt, value );
RaisePropertyChanged( nameof( HasPrompt ) );
}
}
private string _Prompt;
#region CloseCommand
public RelayCommand CloseCommand { get; private set; }
private bool CanCloseDialog() {
return ParserType == DataService.AWK_FORMAT ||
ParserType == DataService.CSHARP_FORMAT ||
ParserType == DataService.REGEX_FORMAT;
}
private void CloseDialog() {
Messenger.Default.Send( new CloseWindowMessage() );
}
#endregion
#region IDataErrorInfo Implementation
public string Error {
get { return this[ "ParserType" ]; }
}
public string this[ string columnName ] {
get {
switch ( columnName ) {
case "ParserType":
return string.IsNullOrWhiteSpace( ParserType ) ? "You must choose a Parser Type" : null;
default:
return null;
}
}
}
#endregion
public NewParserViewModel( IDataService dataService ) {
_dataService = dataService;
CloseCommand = new RelayCommand( CloseDialog, CanCloseDialog );
ParserTypes = new ObservableCollection<string>();
ParserTypes.Add( DataService.AWK_FORMAT );
ParserTypes.Add( DataService.CSHARP_FORMAT );
ParserTypes.Add( DataService.REGEX_FORMAT );
}
}
}
I've placed a breakpoint in the CanCloseDialog method and it only gets hit once, when the dialog is first displayed. If I select a choice in the ComboBox after the dialog is displayed, the OK button doesn't enable.
What am I missing?
I did some more searching & I finally found the answer here. It turns out I had to change the
using GalaSoft.MvvmLight.Command;
statement to
GalaSoft.MvvmLight.CommandWpf;
Doing this, everything works properly.
Try a delegate Func wich call CanCloseDialog instead.
Somehow RelayCommand lose pointer to the method whithout a delegate.
Invoke the delegate in the CanExecute method of the RelayCommand implementation.
Something like :
CloseCommand = new RelayCommand( CloseDialog,() => {return <your condition logic>});
I have a TabViewModel wich contains a dependency property CurrentViewModel. The CurrentViewModel property is bound to a ContentControl in the view TabView.xaml. The TabViewModel also contains a command to change the CurrentViewModel to a ProductViewModel:
public class TabViewModel: BaseViewModel
{
public string TabName { get; set; }
//public List<BaseViewModel> ViewModels { get; set; }
private PageViewModel _currentViewModel;
public PageViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
public TabViewModel(string tabName, PageViewModel currentViewModel)
{
TabName = tabName;
CurrentViewModel = currentViewModel;
}
private ICommand _navigateToProductViewModelCommand;
public ICommand NavigateToProductViewModelCommand
{
get
{
if (_navigateToProductViewModelCommand == null)
{
_navigateToProductViewModelCommand = new DelegateCommand<Product>(
(p) =>
{
CurrentViewModel = new ProductViewModel();
});
}
return _navigateToProductViewModelCommand;
}
}
}
TabView.xaml
<UserControl x:Class="Monitoring_Tool.Views.TabView"
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"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar Value="{Binding Path=CurrentViewModel.PageProgress}" Height="5" Grid.Row="0" Margin="0,0,0,10">
<ProgressBar.Style>
<Style TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderThickness="0,0,0,0" Background="LightGray" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="#00B6FA" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ProgressBar.Style>
</ProgressBar>
<ContentControl Content="{Binding Path=CurrentViewModel}" Grid.Row="1" />
</Grid>
I instantiate the TabViewModel like this:
new TabViewModel("Producten", new ProductsViewModel())
The ProductsView.xaml is shown like it should be. In the ProductsView.xaml I call the command from the TabViewModel like this:
<DataGrid.InputBindings>
<MouseBinding
MouseAction="LeftDoubleClick"
Command="{Binding DataContext.NavigateToProductViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type views:TabView}}}"/>
</DataGrid.InputBindings>
When the datagrid is empty, the command is executed and ProductView.xaml appears like it should be. But when the datagrid is not empty somthing strange happens:
the command is executed, and when I debug I can see that the currentViewModel is changed to ProductViewModel. Then when OnPropertyChanged("CurrentViewModel") is called. There is a set call (value = null) to a depedency property (SelectedAssetCategory) on the ProductsViewModel, wich was replaced and doesn't exits anymore?!
When I put CurrentViewModel = null the same thing happens, I can only do CurrentViewModel = new ProductsViewModel. So I guess it's somthing with updating the UI?
In the App.xaml I defined the following recources:
<DataTemplate DataType="{x:Type viewmodels:TabViewModel}">
<views:TabView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ProductsViewModel}">
<views:ProductsView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ProductViewModel}">
<views:ProductView />
</DataTemplate>
The ProductsViewModel looks like this:
class ProductsViewModel: PageViewModel
{
private readonly MonitotingToolEntities _databaseEntities;
public ProductsViewModel()
{
_databaseEntities = new MonitotingToolEntities();
AssetCategories = new ObservableCollection<AssetCategory>(_databaseEntities.AssetCategory.ToList())
{
new AssetCategory() {AssetCategoryID = 0, AssetCategoryName = "Alles"}
};
Results = new ObservableCollection<Product>();
}
public ObservableCollection<AssetCategory> AssetCategories { get; set; }
private AssetCategory _selectedAssetCategory;
public AssetCategory SelectedAssetCategory
{
get { return _selectedAssetCategory; }
set
{
_selectedAssetCategory = value; //this one is called with value = null
OnPropertyChanged("SelectedAssetCategory");
Filter();
}
}
public ObservableCollection<Product> Results { get; set; }
public void Filter()
{
Results.Clear();
List<Product> products =
SelectedAssetCategory.AssetCategoryID == 0
? _databaseEntities.Product.ToList()
: SelectedAssetCategory.Product.ToList();
foreach (Product product in products)
{
Results.Add(product);
}
}
}
ProductsView.xaml:
<UserControl x:Class="Monitoring_Tool.Views.ProductsView"
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"
xmlns:views="clr-namespace:Monitoring_Tool.Views"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:viewModels="clr-namespace:Monitoring_Tool.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<CollectionViewSource x:Key="CvsAssetCategories" Source="{Binding Path= AssetCategories}" >
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="AssetCategoryID"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="CvsResults" Source="{Binding Path= Results}" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="AssetCategory.AssetCategoryName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style TargetType="Image" x:Key="ImageDisabledStyle">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Margin="0, 0, 0, 10" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Asset Categorie:" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,10,0"/>
<ComboBox Grid.Column="1"
ItemsSource="{Binding Source={StaticResource CvsAssetCategories}}"
DisplayMemberPath="AssetCategoryName"
SelectedItem="{Binding SelectedAssetCategory}"
Margin="0,0,10,0"/>
<TextBlock Text="Zoeken:" Grid.Column="3" VerticalAlignment="Center" Margin="0,0,10,0"/>
<ComboBox Grid.Column="4"
SelectedItem="{Binding SelectedSearchField}"
Margin="0,0,10,0"/>
<TextBox Text="{Binding Path=SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="5" Margin="0,0,10,0">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=SearchCommand}" CommandParameter="{Binding SearchQuery}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="6"
Command="{Binding SearchCommand}"
CommandParameter="{Binding SearchQuery}"
Padding="5,0,5,0" Margin="0,0,10,0" >
<Button.Content>
<Image Source="/Recourses/SearchIcon.png"
Stretch="None" VerticalAlignment="Top" Style="{Binding Source={StaticResource ImageDisabledStyle}}"/>
</Button.Content>
</Button>
<Button Grid.Column="7"
Command="{Binding CancelSearchCommand}"
IsEnabled="{Binding CancelSearchEnabled}"
Padding="5,0,5,0">
<Button.Content>
<Image Source="/Recourses/CancelSearchIcon.png"
Stretch="None" VerticalAlignment="Top" Style="{Binding Source={StaticResource ImageDisabledStyle}}"/>
</Button.Content>
</Button>
</Grid>
<DataGrid Name="DgProducts" AutoGenerateColumns="False"
RowHeaderWidth="0" Margin="0,0,0,10" Grid.Row="1" IsReadOnly="True"
SelectionMode="Single" CanUserReorderColumns="False"
EnableRowVirtualization="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True"
ItemsSource="{Binding Source={StaticResource CvsResults}}" SelectedItem="{Binding SelectedProduct}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.InputBindings>
<MouseBinding
MouseAction="LeftDoubleClick"
Command="{Binding DataContext.NavigateToProductViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type views:TabView}}}"
/>
</DataGrid.InputBindings>
<DataGrid.Resources>
<Style TargetType="DataGridColumnHeader" x:Key="DgVerticalColumnHeader">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="LightGray"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey }"
Color="Black"/>
</DataGrid.Resources>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" Background="DarkGray" Padding="2,0,0,0"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Manager.ManagerName}" Header="Manager" />
<DataGridTextColumn Binding="{Binding Path=ProductName}" Header="Product" />
<DataGridTextColumn Binding="{Binding Path=MonitoringBy}" Header="Monitoring door" />
<DataGridTextColumn Binding="{Binding Path=AumProduct}" Header="AUM Product (mln)" />
<DataGridTextColumn Binding="{Binding Path=AumProductDate, StringFormat='{}{0:dd-MM-yyyy}'}" Header="Datum AUM Product" />
<DataGridTextColumn Binding="{Binding Path=AumStrategy}" Header="AUM Strategie (mln)" />
<DataGridTextColumn Binding="{Binding Path=AumStrategyDate, StringFormat='{}{0:dd-MM-yyyy}'}" Header="Datum AUM Strategie" />
<DataGridTextColumn Binding="{Binding Path=Aum}" Header="AUM (mln)" />
<DataGridTextColumn Binding="{Binding Path=TotalExpenseRatio}" Header="TER (bp)" />
<DataGridTextColumn Binding="{Binding Path=Fee}" Header="Total Fee" />
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Results.Count, StringFormat='{}{0} Producten'}" Grid.Column="0" Margin="0,0,0,0"/>
<Button Grid.Column="2"
Content="Toevoegen"
Padding="5,0,5,0" Margin="0,0,10,0"
Command="{Binding AddProductCommand}" />
<Button Grid.Column="3"
Content="Verwijderen"
Padding="5,0,5,0"
Command="{Binding Path=RemoveProductCommand}"
CommandParameter="{Binding Path=SelectedProduct}"/>
</Grid>
</Grid>
PageViewModel is an abstract class:
public abstract class PageViewModel: BaseViewModel
{
private int _pageProgress;
public int PageProgress
{
get { return _pageProgress; }
set
{
_pageProgress = value;
OnPropertyChanged("PageProgress");
}
}
}
It's kind of weird but it has something to do with the collectionviewsource (CvsAssetCategories) bound to the combobox. If I bind directly to the combobox without using the collectionviewsource the dependency property is not called. However, I like to use the collectionviewsource for the sortdescriptor. My solution now is a not null check on the setter from the dependency property, but I think its nasty way to go.