I have model named EditableSong which is derived from ValidatableModel Class which
implements INotifyPropertyChanged and INotifyDataErrorInfo.
class EditableSong : ValidatableModel
{
CommandRelay addcommand = null;
public ICommand AddCommand
{
get { return addcommand; }
}
public EditableSong()
{
addcommand = new CommandRelay(Add, CanAdd);
}
private void Add(object obj = null)
{
MessageBox.Show("Succesfully Added!");
}
private bool CanAdd(object obj = null)
{
return !HasErrors;
}
private string title;
[Required]
[MaxLength(45)]
public string Title
{
get { return title; }
set
{ SetProperty(ref title, value); }
}
private string lyrics;
[MaxLength(3000)]
public string Lyrics
{
get { return lyrics; }
set { SetProperty(ref lyrics, value); }
}
private string artist;
[Required]
public string Artist
{
get { return artist; }
set {SetProperty(ref artist, value); }
}
}
And here is ValidatableModel class:
class ValidatableModel : BindableBase, INotifyDataErrorInfo
{
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public bool HasErrors
{
get { return _errors.Count >0; ; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null)
{
base.SetProperty(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
public IEnumerable GetErrors(string propertyName)
{
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
protected void ValidateProperty<T>(string propertyName, T value)
{
var results = new List<ValidationResult>();
ValidationContext context = new ValidationContext(this);
context.MemberName = propertyName;
Validator.TryValidateProperty(value, context, results);
if (results.Any())
{
_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
}
else
{
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
protected void OnErrorsChanged(string propName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propName));
}
}`
And it works well, but only after I change properties in textboxes of my window.
The main problem is that user mustn't be able to save model without filling required fields, but when windows loads button Save (which uses command) available, because of validation doesn't run.
Here is xaml:
<Window x:Class="ValidationTests.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:ValidationTests"
xmlns:rules="clr-namespace:ValidationTests.Rules"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<Style x:Key="TextBoxError" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Label>Artist</Label>
<TextBox Style="{StaticResource TextBoxError}" Text="{Binding Artist,
ValidatesOnNotifyDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<Label>Title</Label>
<TextBox Style="{StaticResource TextBoxError}" Text="{Binding Title,
ValidatesOnNotifyDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Label>Lyrics</Label>
<TextBox Style="{StaticResource TextBoxError}" Text="{Binding Lyrics,
ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Content="Add" Command="{Binding AddCommand}"></Button>
</StackPanel>
</Grid>
I wonder how can i fix this...
I wonder how can i fix this...
You need to perform the actual validation upfront.
Call the ValidateProperty method for all properties in the constructor of your EditableSong class to populate the Dictionary and raise the ErrorsChanged event:
public EditableSong()
{
addcommand = new CommandRelay(Add, CanAdd);
ValidateProperty(nameof(Title), title);
ValidateProperty(nameof(Lyrics), lyrics);
ValidateProperty(nameof(Artist), artist);
}
Related
I am a VB.Net programmer and quite new to C#. I am at a point where I'm stuck.
I want to make an app to create a quotation with Word. This Quotation should consist of two Word files.
The Word files are Templates with Bookmarks, so writing to them should be no problem.
I want to have a WPF User Interface where the User can describe the Article and when clicking on a button the two Word files will be created.
I made the WPF User Interface and binded the Textboxes to a cl_Data.cs Class where are Properties such as : Description, FunctionName, etc.
My Problem:
How can i access the Data from the User Interface from my Code Behinde to shift it to the Word files?
The Code:
WPF: How i Bind it on .xaml level
<Window.Resources>
<!-- Binding the Data Class-->
<local:Cl_Data x:Key="Data"
Dealer="Test"
Customer="Tester"
Machine="M***s"
PRJ="123456"
DeliveryTime="6"
Description="Managing different chucks, Saving position data of the linear sensor for chuck clamp unclamp position"
Operation="The operator can select a chuck form the chuck management and save the clamp and unclamp position and reuse this position for next time"
FunctionName="GeneratorAPP"
Requirements="API-Kit"
/>
</Window.Resources>
How i call it on .xaml level (same document) -> This works
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer"
TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer" TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
So my class cl_Data.cs looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows;
namespace QuotationApp.Classes
{
internal class Cl_Data : INotifyPropertyChanged
{
#region Descriptions
private string _Dealer ;
public string Dealer
{
get { return _Dealer; }
set
{ _Dealer = value;
OnPropertyChanged("Dealer");
}
}
private string _Customer;
public string Customer
{
get { return _Customer; }
set
{
_Customer = value;
OnPropertyChanged("Customer");
}
}
private string _Machine;
public string Machine
{
get { return _Machine; }
set
{
_Machine = value;
OnPropertyChanged("Machine");
}
}
private string _PRJ;
public string PRJ
{
get { return _PRJ; }
set
{
_PRJ = value;
OnPropertyChanged(PRJ);
}
}
private string _DeliveryTime;
public string DeliveryTime
{
get { return _DeliveryTime; }
set {
_DeliveryTime = value;
OnPropertyChanged("DeliveryTime");
}
}
private string _Operation;
public string Operation
{
get { return _Operation; }
set {
_Operation = value;
OnPropertyChanged("Operation");
}
}
private string _Description;
public string Description
{
get { return _Description; }
set {
_Description = value;
OnPropertyChanged("Description");
}
}
private string _FunctionName;
public string FunctionName
{
get { return _FunctionName; }
set {
_FunctionName = value;
OnPropertyChanged("FunctionName");
}
}
private string _Requirements;
public string Requirements
{
get { return _Requirements; }
set {
_Requirements = value;
OnPropertyChanged("Requirements");
}
}
#endregion
#region Costs
private double _HardwareCost;
public double HardwareCost
{
get { return _HardwareCost; }
set {
_HardwareCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("HardwareCost");
}
}
private double _PersonalCost;
public double PersonalCost
{
get { return _PersonalCost; }
set {
_PersonalCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("PersonalCost");
}
}
private double _TravelCost;
public double TravelCost
{
get { return _TravelCost; }
set {
_TravelCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("TravelCost");
}
}
private double _CostTotal;
public double CostTotal
{
get { return _CostTotal; }
set {
_CostTotal = value;
OnPropertyChanged("CostTotal");
}
}
public double CalcTotal()
{
double total = 0;
try
{
total = TravelCost + HardwareCost + PersonalCost;
}
catch (Exception e)
{
MessageBox.Show("Error getting the total Value: " + e.Message);
}
return total;
}
#endregion
#region PropertyChangedEvents
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
So now i want to access these Data for example the Description (Data.Description) to process it to a word Bookmark. But how can i Access this Data on WPF level from CodeBehind?
Please be easy with me, i know this question is wierd but i googled 2 days now an i am starting to get frustrated. If this question is answered somewhere else, i would love to have the link to the answer.
Thanks in advance
I made the most simple example as far as it came to my mind.
If you don't understand, ask questions about it.
I will try to answer.
using System;
namespace Core2022.Lexxy_B
{
public class PersonDto
{
public int Id { get; }
public string Name { get; }
public int Age { get; }
public PersonDto(int id, string name, int age)
{
if (Id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
Id = id;
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
Name = name;
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
Age = age;
}
public PersonDto(string name, int age)
: this(0, name, age)
{
Id = -1;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Core2022.Lexxy_B
{
public class PeopleModel
{
private readonly List<PersonDto> people = new List<PersonDto>()
{
new PersonDto(5, "Thomas", 25),
new PersonDto(1, "Harry", 40),
};
public IReadOnlyList<PersonDto> GetPeople() => Array.AsReadOnly(people.ToArray());
public void AddPerson(PersonDto person)
{
int id = people.LastOrDefault()?.Id ?? 0;
do
{
id++;
} while (people.Any(p => p.Id == id));
person = new PersonDto(id, person.Name, person.Age);
people.Add(person);
AddedPerson?.Invoke(this, person);
}
public event EventHandler<PersonDto>? AddedPerson;
}
}
namespace Core2022.Lexxy_B
{
public class PersonVM
{
public string? Name { get; set; }
public int Age { get; set; }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace Core2022.Lexxy_B
{
public class PeopleViewModel : ViewModelBase
{
private readonly PeopleModel model = new PeopleModel();
private string _mode = "view";
public ObservableCollection<PersonDto> People { get; } = new ObservableCollection<PersonDto>();
public string ViewMode { get => _mode; private set => Set(ref _mode, value); }
public PeopleViewModel()
{
foreach (var person in model.GetPeople())
{
People.Add(person);
}
model.AddedPerson += OnAddedPerson;
}
private void OnAddedPerson(object? sender, PersonDto newPerson)
{
People.Add(newPerson);
}
public RelayCommand AddPersonCommand => GetCommand<PersonVM>(AddPersonExecute, AddPersonCanExecute);
private void AddPersonExecute(PersonVM person)
{
model.AddPerson(new PersonDto(person.Name ?? string.Empty, person.Age));
ViewMode = "view";
}
private bool AddPersonCanExecute(PersonVM person)
{
return !string.IsNullOrWhiteSpace(person.Name) && person.Age >= 0;
}
public RelayCommand ExitAddingPersonCommand => GetCommand(() => ViewMode = "view");
public RelayCommand BeginAddingPersonCommand => GetCommand(() => ViewMode = "add");
}
}
<Window x:Class="Core2022.Lexxy_B.PeopleWindow"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
Title="PeopleWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:PeopleViewModel x:Key="vm"/>
</Window.Resources>
<UniformGrid Columns="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="lBox" ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
<Button Grid.Row="1" Content="Go to Add Person" Padding="15 5" Margin="5"
Command="{Binding BeginAddingPersonCommand}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="add">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<ContentControl x:Name="cp">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Resources>
<DataTemplate x:Key="view.Template">
<local:PersonDetailsUC/>
</DataTemplate>
<DataTemplate x:Key="add.Template">
<local:AddPersonUC/>
</DataTemplate>
</Style.Resources>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate" Value="{StaticResource add.Template}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="view">
<Setter Property="Content" Value="{Binding SelectedItem, ElementName=lBox}"/>
<Setter Property="ContentTemplate" Value="{StaticResource view.Template}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UniformGrid>
</Window>
<UserControl x:Class="Core2022.Lexxy_B.PersonDetailsUC"
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:local="clr-namespace:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:PersonDto}">
<UniformGrid Columns="1">
<TextBlock Text="{Binding Id, StringFormat={}Id: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Name, StringFormat={}Name: {0}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Age, StringFormat={}Age: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UniformGrid>
</UserControl>
<UserControl x:Class="Core2022.Lexxy_B.AddPersonUC"
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:local="clr-namespace:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<d:UserControl.DataContext>
<local:PeopleViewModel/>
</d:UserControl.DataContext>
<UserControl.Resources>
<local:PersonVM x:Key="person"/>
</UserControl.Resources>
<UniformGrid Columns="2">
<TextBlock Text="Name" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Name, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<TextBlock Text="Age" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Age, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<Button Content="Add" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding AddPersonCommand}"
CommandParameter="{Binding Mode=OneWay, Source={StaticResource person}}"/>
<Button Content="Exit" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding ExitAddingPersonCommand}"/>
</UniformGrid>
</UserControl>
Addition due to the Repository.
change Line 33 of Word.cs as i was unable to assign a relative path to open to word template
An example of the implementation of obtaining the full name of files by their path given by a relatively executable assembly.
The file itself in the Project resources must have the properties "Content" - "Copy ...". I have a Russified Studio, so the screenshot is in Russian.
The "bin" folder must not be included in the Project, otherwise, all its contents will also be included in the Assembly.
And here is the code for converting a relative path to an absolute one:
public static readonly string ApplicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public const string TestRelativeNameFile = #"Resources\Word\Test.docx";
public static readonly string TestFullNameFile = Path.Combine(ApplicationFolder, TestRelativeNameFile);
public static void GenerateLocalSolution()
{
try
{
WordApp = new Microsoft.Office.Interop.Word.Application();
TestApp = WordApp.Documents.Open(TestFullNameFile);
}
i bind the DataContext in MainWindow.cs (CodeBehind) to a Class and create a specific Instance of the Object which i access from every other class
This implementation doesn't play well with WPF and OOP.
For example, in Designer mode (when you edit XAML in Studio), you have an empty value in the DataContext. And because of this, you can't use the Binding Builder in development.
If you have an instance of the Cl_Data class specific to each MainWindow instance, then it should be initialized in the Window's XAML:
<Window.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Window.Resources>
If the Cl_Data instance is the only one in the application sense and the entire session exists, then you can create it in the App resources:
<Application.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Application.Resources>
The window gets it in the DataContext in XAML too.
If its value is needed in the Code Behind, then it must be retrieved either by key or from the DataContext.
<Window x:Class="QuoteApp_EldHasp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
------------------
Title="MainWindow" Height="900" Width="1800"
DataContext="{DynamicResource data}">
// Creating such a public property is a bad idea. But for now, I'm not getting involved.
public /* static */ Cl_Data Data { get; } //= new Cl_Data();
public MainWindow()
{
InitializeComponent();
Data = (Cl_Data)DataContext;
// Or
Data = (Cl_Data)FindResource("data");
}
Next note.
In the Word.GenerateLocalSolution() method, the line "descRange.Text = MainWindow.Data.Dealer" is a VERY STRONG OOP violation. The Word class contains logic for working with data (Domain Logic) - this is its "single-responsibility". And it should not work with UI elements. And "MainWindow.Data" is the View property!
There are several options for correct implementation.
One of them is to get the desired value in the parameter and let the one who called this method decide where to get it from.
public static void GenerateLocalSolution(string text)
{
// Some Code
descRange.Text = text; // MainWindow.Data.Dealer;
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
var data = (Cl_Data)DataContext;
Word.GenerateLocalSolution(data.Dealer);
}
You should also replace clickers with commands. This will significantly improve the architecture of the application and make the code easier.
I'm using BindingGroup to track changes to the data in my ViewModel so I can save them or roll back. My View looks something like this:
<UserControl x:Class="UserManagement.Client.Views.UserDetail"
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:prism="http://prismlibrary.com/"
xmlns:viewModels="clr-namespace:UserManagement.Client.ViewModels"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:IUserDetailViewModel}"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup x:Name="BindingGroup">
<BindingGroup.ValidationRules>
<client:UserDetailValidation ValidationStep="ConvertedProposedValue"/>
</BindingGroup.ValidationRules>
</BindingGroup>
</StackPanel.BindingGroup>
<TextBlock Text="Display Name:"/>
<TextBox Text="{Binding User.DisplayName}"/>
<ListView ItemsSource="{Binding UserGroups}">
<ListView.View>
<GridView>
<GridViewColumn Header="Display Name" DisplayMemberBinding="{Binding DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Edit Groups" Command="{Binding EditGroupsCommand}"/>
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding ElementName=BindingGroup}"/>
<Button Content="Cancel" Command="{Binding CancelCommand}" CommandParameter="{Binding ElementName=BindingGroup}"/>
</StackPanel>
</UserControl>
My ViewModel looks like this:
public class UserDetailViewModel : BindableBase, IUserDetailViewModel
{
private const string DialogParameterNameAssignedGroups = "AssignedGroups";
private const string DialogTitle = "GroupSelection";
private readonly IDialogService _dialogService;
private readonly IUserClientService _userClientService;
private User _user;
public UserDetailViewModel(IDialogService dialogService, IUserClientService userClientService)
{
_dialogService = dialogService;
_userClientService = userClientService;
SaveCommand = new DelegateCommand<BindingGroup>(Save, CanSave);
CancelCommand = new DelegateCommand<BindingGroup>(Cancel, CanCancel);
EditGroupsCommand = new DelegateCommand(EditGroups, CanEditGroups);
}
public User User
{
get => _user;
set
{
SetProperty(ref _user, value);
RaisePropertyChanged(nameof(User));
RaisePropertyChanged(nameof(UserGroups));
}
}
public ICollection<Group> UserGroups => new ObservableCollection<Group>(User.Groups);
public DelegateCommand<BindingGroup> SaveCommand { get; }
public DelegateCommand EditGroupsCommand { get; }
public DelegateCommand<BindingGroup> CancelCommand { get; }
private static bool CanSave(BindingGroup bindingGroup)
{
return bindingGroup.IsDirty;
}
private void Save(BindingGroup bindingGroup)
{
if (!bindingGroup.CommitEdit()) return;
bindingGroup.BeginEdit();
_userClientService.UpdateUser(User);
}
private static bool CanCancel(BindingGroup bindingGroup)
{
return bindingGroup.IsDirty;
}
private static void Cancel(BindingGroup bindingGroup)
{
bindingGroup.CancelEdit();
bindingGroup.BeginEdit();
}
private bool CanEditGroups()
{
return _user != null;
}
private void EditGroups()
{
_dialogService.ShowDialog(DialogTitle, new DialogParameters
{
{DialogParameterNameAssignedGroups, new ObservableCollection<Group>(_user.Groups)}
}, HandleDialogResult);
}
private void HandleDialogResult(IDialogResult dialogResult)
{
if (dialogResult.Result != ButtonResult.OK) return;
var assignedGroups = dialogResult.Parameters.GetValue<ICollection<Group>>(
DialogParameterNameAssignedGroups);
_user.Groups.Clear();
foreach (var assignedGroup in assignedGroups)
{
_user.Groups.Add(assignedGroup);
}
RaisePropertyChanged(nameof(UserGroups));
}
}
The validation class does nothing:
public class UserDetailValidation : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
return ValidationResult.ValidResult;
}
}
This code works fine, if I change the UserName. I can save the change or cancel (either option will disable the buttons). However, if I change the UserGroups collection (with a seperate dialog), the buttons will not enable, but the UI will update.
What do I have to change that the BindingGroup sets IsDirty when I change the UserGroups property?
I might miss something but why don't you just set IsDirty=true in HandleDialogResult ?
Otherwise ObservableCollection provides an event CollectionChanged which you can register for and get notification whenever that collection is changed, so you can probably change isDirty there.
I've created my own UserControl, called PersonNameControl which is intented to be reused.
The control has three TextBox fields, and has three dependency properties in its class file.
Firstname
Insertion
Lastname
Each dependency property value is bound to to a field, so the dependency property Firstname is bound to the Firstname TextBox, and so on.
I conciously didn't explicitly set the DataContext of the UserControl.
The control should be as loosely as possible. It should only get it's values (for the fields) via its dependency properties. It shouldn't even be looking to anything like DataContext.
<UserControl x:Class="WpfApplication1.PersonNameControl">
<StackPanel>
<Label>Firstname:</Label>
<TextBox Text="{Binding Firstname, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
<Label>Insertion:</Label>
<TextBox Text="{Binding Insertion, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
<Label>Lastname:</Label>
<TextBox Text="{Binding Lastname, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBox>
</StackPanel>
</UserControl>
And the control class:
public partial class PersonNameControl : UserControl
{
public PersonNameControl()
{
InitializeComponent();
}
public string Firstname
{
get { return (string)GetValue(FirstnameProperty); }
set { SetValue(FirstnameProperty, value); }
}
public static readonly DependencyProperty FirstnameProperty =
DependencyProperty.Register("Firstname", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Insertion
{
get { return (string)GetValue(InsertionProperty); }
set { SetValue(InsertionProperty, value); }
}
public static readonly DependencyProperty InsertionProperty =
DependencyProperty.Register("Insertion", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Lastname
{
get { return (string)GetValue(LastnameProperty); }
set { SetValue(LastnameProperty, value); }
}
public static readonly DependencyProperty LastnameProperty =
DependencyProperty.Register("Lastname", typeof(string), typeof(PersonNameControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
The control is supposed to be used inside another view as follows:
<!--
Here we are inside a view or some other control.
The bindings here provide the dependency properties of the UserControl with a value.
The DataContext of the view where my UserControl is used, is a ViewModel that implements INotifyDataErrorInfo
-->
<myControls:PersonNameControl
Firstname="{Binding SomeFirstnameFromVM, Mode=TwoWay}"
Insertion="{Binding SomeInsertionFromVM, Mode=TwoWay}"
Lastname="{Binding SomeLastnameFromVM, Mode=TwoWay}">
</myControls:PersonNameControl>
When the ViewModel (implements INotifyDataErrorInfo) created a Validation error, nothing happens with my PersonNameControl UserControl.
I managed in making a control that is independent because it doesn't rely on a specific DataContext, doesn't set its own DataContext in its codebehind file, and just gets its values via dependency properties. The values are exchanged via the bindings and show up, but the validation errors don't show.
What I want is passing the validation errors through, to the UserControl.
Some solutions on the internet make use of ValidationAdornerSite and I tried this. But this would only work for one TextBox.
I don't see any solution without making my control dependent on the outside world or introducing ugly extra properties to solve it cumbersome. I thought the errors are 'tunneled' like a piece of information through all bindings towards the last level where the value arrives. But this seems not to be the right consideration.
Edit:
I added my ViewModel class.
public class CustomerFormViewModel : ViewModelBase, INotifyDataErrorInfo
{
protected string _clientNumber;
protected DateTime _date;
protected string _firstname;
protected string _insertion;
protected string _lastname;
protected Address _address;
protected ObservableCollection<Email> _emails;
protected ObservableCollection<PhoneNumber> _phoneNumbers;
protected string _note;
protected bool _hasErrors;
protected IList<ValidationFailure> _validationErrors;
public IList<ValidationFailure> ValidationErrors
{
get { return _validationErrors; }
set { _validationErrors = value; OnPropertyChanged("ValidationErrors"); }
}
public string ClientNumber
{
get { return _clientNumber; }
set { _clientNumber = value; OnPropertyChanged("ClientNumber"); }
}
public DateTime Date
{
get { return _date; }
set { _date = value; OnPropertyChanged("Date"); }
}
public string Firstname
{
get { return _firstname; }
set { _firstname = value; OnPropertyChanged("Firstname"); }
}
public string Insertion
{
get { return _insertion; }
set { _insertion = value; OnPropertyChanged("Insertion"); }
}
public string Lastname
{
get { return _lastname; }
set { _lastname = value; OnPropertyChanged("Lastname"); }
}
public Address Address
{
get { return _address; }
set { _address = value; OnPropertyChanged("Address"); }
}
public ObservableCollection<Email> Emails
{
get { return _emails; }
set { _emails = value; OnPropertyChanged("Emails"); }
}
public ObservableCollection<PhoneNumber> PhoneNumbers
{
get { return _phoneNumbers; }
set { _phoneNumbers = value; OnPropertyChanged("PhoneNumbers"); }
}
public string Note
{
get { return _note; }
set { _note = value; OnPropertyChanged("Note"); }
}
private DelegateCommand _saveCustomerCommand;
public DelegateCommand SaveCustomerCommand
{
get { return _saveCustomerCommand; }
private set { _saveCustomerCommand = value; OnPropertyChanged("SaveCustomerCommand"); }
}
public CustomerFormViewModel()
{
ValidationErrors = new List<ValidationFailure>();
SaveCustomerCommand = new DelegateCommand(SaveCustomer, CanSaveCustomer);
}
protected void ValidateInput()
{
ValidationErrors.Clear();
CustomerFormValidator validator = new CustomerFormValidator();
FluentValidation.Results.ValidationResult result = validator.Validate(this);
ValidationErrors = result.Errors;
foreach (ValidationFailure f in ValidationErrors)
{
Console.WriteLine(f.ErrorMessage);
}
_hasErrors = result.Errors.Count != 0;
List<string> vmProperties = new List<string>() { "Firstname", "Lastname", "Address", "ClientNumber", "Date" };
foreach (string propertyName in vmProperties)
{
OnErrorsChanged(propertyName);
}
}
public bool HasErrors
{
get { return _hasErrors; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
protected void OnErrorsChanged(string name)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(name));
}
public IEnumerable GetErrors(string propertyName)
{
return ValidationErrors.Where<ValidationFailure>(x => x.PropertyName == propertyName);
}
public void SaveCustomer(object parameter)
{
this.ValidateInput();
if( ! HasErrors)
{
Customer customer = new Customer(-1, ClientNumber, Date, Firstname, Insertion, Lastname, Address);
ICustomerRepository repo = new CustomerRepository();
bool res = repo.SaveCustomer(customer);
if(res) {
// ...
}
// ...
} else
{
MessageBox.Show("One or more fields are not filled in correctly.", "Invalid input", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public bool CanSaveCustomer(object parameter)
{
return true;
}
}
So, I have prepared a demo user control. It is a sub user control, gets all validation info from its MainViewModel
MainWindow
<Window
x:Class="ValidationSubUI.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:local="clr-namespace:ValidationSubUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Name="MyWindow"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<local:SubUserControl
FirstName="{Binding FirstName, Mode=TwoWay}"
LastName="{Binding LastName, Mode=TwoWay}"
ValidationSource="{Binding ElementName=MyWindow, Path=DataContext}" />
</Grid>
</Window>
MainViewModel
using GalaSoft.MvvmLight;
using System.ComponentModel;
namespace ValidationSubUI
{
public class MainViewModel : ViewModelBase, IDataErrorInfo
{
public string Error
{
get
{
return string.Empty;
}
}
private string m_FirstName;
public string FirstName
{
get { return m_FirstName; }
set
{
m_FirstName = value;
RaisePropertyChanged();
}
}
private string m_LastName;
public string LastName
{
get { return m_LastName; }
set
{
m_LastName = value;
RaisePropertyChanged();
}
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
return GetFirstNameError();
}
else if (columnName == nameof(LastName))
{
return GetLastNameError();
}
return null;
}
}
private string GetFirstNameError()
{
string result = string.Empty;
if (string.IsNullOrEmpty(FirstName))
{
result = "First name required";
}
return result;
}
private string GetLastNameError()
{
string result = string.Empty;
if (string.IsNullOrEmpty(LastName))
{
result = "Last name required";
}
return result;
}
}
}
SubUserControl gets all validation logic from MainViewModel
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace ValidationSubUI
{
/// <summary>
/// Interaction logic for SubUserControl.xaml
/// </summary>
public partial class SubUserControl : UserControl, IDataErrorInfo
{
public SubUserControl()
{
InitializeComponent();
}
public IDataErrorInfo ValidationSource
{
get { return (IDataErrorInfo)GetValue(ValidationSourceProperty); }
set { SetValue(ValidationSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ValidationSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValidationSourceProperty =
DependencyProperty.Register("ValidationSource", typeof(IDataErrorInfo), typeof(SubUserControl), new PropertyMetadata(null));
public string FirstName
{
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
// Using a DependencyProperty as the backing store for FirstName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));
public string LastName
{
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
// Using a DependencyProperty as the backing store for LastName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register("LastName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));
public string Error
{
get
{
return string.Empty;
}
}
public string this[string columnName]
{
get
{
if (ValidationSource != null)
{
return ValidationSource[columnName];
}
return null;
}
}
}
}
and SubUserControl
<UserControl
x:Class="ValidationSubUI.SubUserControl"
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"
x:Name="CustomControl"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError" />
</Border>
<TextBlock
Margin="5,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
FontWeight="DemiBold"
Foreground="Red"
Text="{Binding ElementName=controlWithError, Path=AdornedElement.ToolTip, Mode=OneWay}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
</UserControl.Resources>
<Grid DataContext="{x:Reference Name=CustomControl}">
<StackPanel>
<TextBox
Width="120"
Height="30"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}"
TextWrapping="Wrap" />
<TextBox
Width="120"
Height="30"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</UserControl>
I have some WPF code that looks like this
C#
namespace ItemEventTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(new MyItem { Name = "Otto" });
MyItems.Add(new MyItem { Name = "Dag" });
MyItems.Add(new MyItem { Name = "Karin" });
InitializeComponent();
}
public ObservableCollection<MyItem> MyItems { get; set; }
}
public class MyItem :INotifyPropertyChanged
{
private string m_name;
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged();
}
}
public void WhenMouseMove()
{
//Do stuff
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml
<Window x:Class="ItemEventTest.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:ItemEventTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
What I now want to do is when the mouse moves over an item is to call WhenMouseMove on the object that is the source of that item.
I would like to have the function called directly on the object and not by first going through MainWindow or some view model connected to MainWindow. It feels like it should be possible because the data is bound that way but I haven't managed to find a description of how to do it.
If you are seeking solution in MVVM pattern
Edit: Add a reference to System.Windows.Interactivity dll (which is system defined if Blend or VS2015 installed)
then add following namespace xmlns:i="schemas.microsoft.com/expression/2010/interactivity"
// xaml file
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseHoveredItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
// viewmodel (MyItem class)
public void WhenMouseMove()
{
//Do stuff
Console.WriteLine(Name);
}
private RelayCommand _MouseHoveredItemChangedCommand;
public RelayCommand MouseHoveredItemChangedCommand
{
get
{
if (_MouseHoveredItemChangedCommand == null)
{
_MouseHoveredItemChangedCommand = new RelayCommand(WhenMouseMove);
}
return _MouseHoveredItemChangedCommand;
}
}
// RelayCommand class
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
I am trying to add to the TreeView the ability to catch the IsExpanded event. So that when a certain Item will expand it will raise a property or command on the view model.
Here is my code:
<TreeView Name="ScenariosTreeView" ItemsSource="{Binding Path=Cat, Mode=TwoWay}" Focusable="True" >
<TreeView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedValue ,ElementName=ScenariosTreeView}" />
</TreeView.InputBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExtended, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type sotc:ScenarioCategory}" ItemsSource="{Binding Path=ScenarioList}" >
<TextBlock Text="{Binding Path=Name}" Foreground="Black" IsEnabled="True" Focusable="True"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sotc:Scenario}" >
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
I also have a property in the viewmodel for IsExpanded (and for IsSelected) and none of that raises when I am expand the TreeviewItem.
Any Ideas?
Thanks ahead,
Tried to reproduce the issue and implemented attached behavior for calling command when node is expanded and everything works fine. Complete code for the solution:
XAML
<Window x:Class="TreeViewTest.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:sotc="clr-namespace:TreeViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<sotc:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TreeView Name="ScenariosTreeView" ItemsSource="{Binding Path=Cat, Mode=TwoWay}" Focusable="True" >
<TreeView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedValue ,ElementName=ScenariosTreeView}" />
</TreeView.InputBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExtended, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="sotc:Behaviours.ExpandingBehaviour" Value="{Binding DataContext.ExpandingCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type sotc:ScenarioCategory}" ItemsSource="{Binding Path=ScenarioList}" >
<TextBlock Text="{Binding Path=Name}" Foreground="Black" IsEnabled="True" Focusable="True"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sotc:Scenario}" >
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
C#
public class ViewModelBase : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MainWindowViewModel : ViewModelBase
{
public ICommand ExpandingCommand { get; set; }
private void ExecuteExpandingCommand(object obj)
{
Console.WriteLine(#"Expanded");
}
private bool CanExecuteExpandingCommand(object obj)
{
return true;
}
public MainWindowViewModel()
{
ExpandingCommand = new RelayCommand(ExecuteExpandingCommand, CanExecuteExpandingCommand);
Cat = new ObservableCollection<ScenarioCategory>(new ScenarioCategory[]
{
new ScenarioCategory { Name = "C1" }, new ScenarioCategory { Name = "C2" }, new ScenarioCategory { Name = "C3" }
});
}
public ObservableCollection<ScenarioCategory> Cat { get; set; }
}
public class ScenarioCategory : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
bool _isExtended;
public bool IsExtended
{
get { return _isExtended; }
set
{
_isExtended = value;
Console.WriteLine(#"IsExtended set");
OnPropertyChanged();
}
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
Console.WriteLine(#"IsSelected set");
OnPropertyChanged();
}
}
public ObservableCollection<Scenario> ScenarioList { get; set; }
public ScenarioCategory()
{
ScenarioList = new ObservableCollection<Scenario>(new Scenario[] { new Scenario { Name = "1" }, new Scenario { Name = "2" }, new Scenario { Name = "3" } });
}
}
public class Scenario : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
bool _isExtended;
public bool IsExtended
{
get { return _isExtended; }
set
{
_isExtended = value;
OnPropertyChanged();
}
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public static class Behaviours
{
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand)o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
}
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
Found the answer. It fired the event if it is placed in the object represented as a treeviewItem and not in the view model.