I want to create the game OXO on WPF. I added the columns in my DataGrid but I want to add the rows without a value. When the user click on the cell, I want that the cell change his value to "X" or "O" depends on the user's turn. Please guide me. How can I manage it ?
This is my grid at the moment :
<Grid >
<DataGrid x:Name="grid"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="237" Width="300"
Margin="27,23,0,0" AutoGenerateColumns="False" FrozenColumnCount="3" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Width="100"/>
<DataGridTextColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Width="100"/>
<DataGridTextColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Thank you guys !
You can use 3 rows and 3 columns in a grid and use checkboxes as content. And then you can style the Checkbox so that it displays X or O on toggle. Below is a sample xaml that I quickly wrote. Please let me know if it helps
<Grid Width="200" Height="200" ShowGridLines="True">
<Grid.Resources>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid Background="Transparent">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="txt" Foreground="Black"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="Text" TargetName="txt" Value="X"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="False">
<Setter Property="Text" TargetName="txt" Value="O"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="0" Grid.Column="1"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="0" Grid.Column="2"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="1" Grid.Column="0"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="1" Grid.Column="1"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="1" Grid.Column="2"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="2" Grid.Column="0"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="2" Grid.Column="1"/>
<CheckBox Content="RadioButton" IsChecked="{x:Null}" Grid.Row="2" Grid.Column="2"/>
</Grid>
DataGrid is not the right choice for this requirement.
I suggest laying out 9 buttons (with empty content) in a regular Grid and handling the click events from there. After a click, simply change the button content to X or O.
OK here is an example that i have knocked up, #Gusdor i did use buttons in the end BUT not 9 of them!!....
The example checks for winning lines and stops the game as soon as one player has won, you then press the reset button to start again....
Start a new wpf project, call it noughtsandcrosses....Paste the following code into your MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:noughtsandcrosses" x:Class="noughtsandcrosses.MainWindow"
Title="MainWindow" Height="386.551" Width="270.253">
<Window.Resources>
<local:GameOverToEnabledConverter x:Key="GameOverToEnabledConverter" />
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Button Content="Reset" Command="{Binding ResetClickCommand}" IsEnabled="{Binding GameOver}" Margin="0,272,0,0"/>
<DataGrid SelectionUnit="Cell" ItemsSource="{Binding rows, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" x:Name="grid"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="257" Width="240"
Margin="10,10,0,0" AutoGenerateColumns="False" FrozenColumnCount="3" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="75">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button IsEnabled="{Binding DataContext.GameOver, Converter={StaticResource GameOverToEnabledConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Content="{Binding Cell1.State}" Command="{Binding DataContext.ButtonClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Cell1}" FontSize="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="75"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="75">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button IsEnabled="{Binding DataContext.GameOver, Converter={StaticResource GameOverToEnabledConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Content="{Binding Cell2.State}" Command="{Binding DataContext.ButtonClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Cell2}" FontSize="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="75"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="75">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button IsEnabled="{Binding DataContext.GameOver, Converter={StaticResource GameOverToEnabledConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Content="{Binding Cell3.State}" Command="{Binding DataContext.ButtonClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Cell3}" FontSize="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="75"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Create a new class called RelayCommand.cs and paste the following code into it....
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace noughtsandcrosses
{
public class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
///<summary>
///Defines the method that determines whether the command can execute in its current state.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
///<returns>
///true if this command can be executed; otherwise, false.
///</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
///<summary>
///Occurs when changes occur that affect whether or not the command should execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
///<summary>
///Defines the method to be called when the command is invoked.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
}
Create a new class called ViewModel.cs and paste the following code into it....
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
namespace noughtsandcrosses
{
public class ViewModel : INotifyPropertyChanged
{
public RelayCommand<Cell> ButtonClickCommand { get; set; }
public RelayCommand<string> ResetClickCommand { get; set; }
public int NoughtsScore { get; set; }
public int CrossesScore { get; set; }
public int TotalScore { get; set; }
private List<Row> _test;
public List<Row> rows
{
get { return _test; }
set { _test = value; }
}
private Cell.CellState _turn;
public Cell.CellState turn
{
get { return _turn; }
set { _turn = value; }
}
private bool _GameOver;
public bool GameOver
{
get { return _GameOver; }
set { _GameOver = value; NotifyPropertyChanged("GameOver"); }
}
public ViewModel()
{
ButtonClickCommand = new RelayCommand<Cell>(OnButtonClickCommand);
ResetClickCommand = new RelayCommand<string>(OnResetClickCommand);
// Start with Noughts going first
this.turn = Cell.CellState.Noughts;
// Add 3 blank rows
this.rows = new List<Row>();
this.rows.Add(new Row());
this.rows.Add(new Row());
this.rows.Add(new Row());
}
private void OnResetClickCommand(string obj)
{
this.rows.Select(c => { c.Cell1.State = GetEnumDescription(Cell.CellState.None); c.Cell2.State = GetEnumDescription(Cell.CellState.None); c.Cell3.State = GetEnumDescription(Cell.CellState.None); return c; }).ToList();
this.NoughtsScore = 0;
this.CrossesScore = 0;
this.GameOver = false;
this.TotalScore = 0;
}
private void OnButtonClickCommand(Cell obj)
{
bool winner = false;
if (obj.State == null)
{
obj.State = GetEnumDescription(this.turn);
// swap user..... don't like this but for 20min wonder someone else can fix it
if (this.turn == Cell.CellState.Noughts)
{
NoughtsScore += obj.Mask;
this.turn = Cell.CellState.Crosses;
winner = CheckForWinner(NoughtsScore);
}
else
{
CrossesScore += obj.Mask;
this.turn = Cell.CellState.Noughts;
winner = CheckForWinner(CrossesScore);
}
TotalScore += obj.Mask;
}
if (winner || CheckForGameOver(this.TotalScore))
this.GameOver = true;
}
private bool CheckForWinner(int Score)
{
if ((Score & 7) == 7)
return true;
if ((Score & 56) == 56)
return true;
if ((Score & 448) == 448)
return true;
if ((Score & 73) == 73)
return true;
if ((Score & 146) == 146)
return true;
if ((Score & 292) == 292)
return true;
if ((Score & 273) == 273)
return true;
if ((Score & 84) == 84)
return true;
return false;
}
private bool CheckForGameOver(int TotalScore)
{
if ((TotalScore & 511) == 511)
return true;
return false;
}
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class Row
{
public Cell Cell1 { get; set; }
public Cell Cell2 { get; set; }
public Cell Cell3 { get; set; }
public Row()
{
this.Cell1 = new Cell();
this.Cell2 = new Cell();
this.Cell3 = new Cell();
}
}
public class Cell : INotifyPropertyChanged
{
private static int power = 0;
public enum CellState
{
[Description(null)]
None,
[Description("O")]
Noughts,
[Description("X")]
Crosses
}
private string _State;
public string State
{
get { return _State; }
set { _State = value; NotifyPropertyChanged("State"); }
}
private int _Mask;
public int Mask
{
get { return _Mask; }
set { _Mask = value; }
}
public Cell()
{
this.State = GetEnumDescription(CellState.None);
this.Mask = 1 << power;
power++;
}
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
create a new class called GameOverToEnabledConverter.cs (simply inverts a bool value to set the button enabled property) and paste the following code into it....
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace noughtsandcrosses
{
class GameOverToEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
}
Related
Trying to get DataContext in UserControl.
My
structure
I have the model Car
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace AutoShop.MVVM.Model
{
class Car : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
private string _Model;
public string Model
{
get
{
return _Model;
}
set
{
_Model = value;
OnPropertyChanged();
}
}
private string _Mark;
public string Mark
{
get
{
return _Mark;
}
set
{
_Mark = value;
OnPropertyChanged();
}
}
private float _Volume;
public float Volume
{
get
{
return _Volume;
}
set
{
_Volume = value;
OnPropertyChanged();
}
}
private int _DateOfIssue;
public int DateOfIssue
{
get
{
return _DateOfIssue;
}
set
{
_DateOfIssue = value;
OnPropertyChanged();
}
}
public enum EngineTypes {
DISEL,
PETROL
};
private EngineTypes _EngineType;
public EngineTypes EngineType
{
get
{
return _EngineType;
}
set
{
_EngineType = value;
OnPropertyChanged();
}
}
private string _ImageUrl;
public string ImageUrl
{
get
{
return _ImageUrl;
}
set
{
_ImageUrl = value;
OnPropertyChanged();
}
}
public Car()
{
}
}
}
And I have main view model
using AutoShop.MVVM.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace AutoShop.MVVM.ViewModel
{
class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public HomeViewModel HomeVM;
private object _CurrentPage;
public object CurrentPage
{
get
{
return _CurrentPage;
}
set
{
_CurrentPage = value;
OnPropertyChanged();
}
}
private List<Car> _Cars;
public List<Car> Cars
{
get
{
return _Cars;
}
set
{
_Cars = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
Cars = new List<Car>() {
new Car
{
Mark = "Audi",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
},
new Car
{
Mark = "Moto",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
},
new Car
{
Mark = "Some",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
}
};
HomeVM = new HomeViewModel();
CurrentPage = HomeVM;
}
}
}
I need to display cars on the ProductPage.xaml and I do it by the next way
<UserControl x:Class="AutoShop.MVVM.View.ProductPage"
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:AutoShop.MVVM.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<StackPanel Background="#fff">
<WrapPanel>
<Grid Width="200px" Margin="30 0 0 0">
<TextBox x:Name="field4" Tag="C:\Users\user\Desktop\Learning\Весенний семестр\ООТП\AutoShop\AutoShop\Images\u.png" Style="{StaticResource TextBoxTemplate}" />
<TextBlock IsHitTestVisible="False" Text="Марка" Padding="20 10 0 0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="FontFamily" Value="/Fonts/#Montserrat Light" />
<Setter Property="Foreground" Value="#ccc" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=field4}" Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid Width="200px" Margin="30 0 0 0">
<TextBox x:Name="field7" Tag="C:\Users\user\Desktop\Learning\Весенний семестр\ООТП\AutoShop\AutoShop\Images\u.png" Style="{StaticResource TextBoxTemplate}" />
<TextBlock IsHitTestVisible="False" Text="username" Padding="20 10 0 0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="FontFamily" Value="/Fonts/#Montserrat Light" />
<Setter Property="Foreground" Value="#ccc" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=field7}" Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid Width="200px" Margin="30 0 0 0">
<ComboBox Style="{StaticResource ComboBoxTheme}" SelectedIndex="0">
<ComboBoxItem>
<TextBlock Text="asdasdasd" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="fsdfsd" />
</ComboBoxItem>
</ComboBox>
</Grid>
</WrapPanel>
<Grid Height="400">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsWrapper" ItemsSource="{Binding Cars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" BorderBrush="Black" BorderThickness="2" Height="279">
<Grid Height="279" Width="200">
<Image Source="{Binding Path=Image}" Width="100" Height="100" VerticalAlignment="Top" Margin="0 10 0 0" />
<TextBlock Text="{Binding Path=Name, StringFormat='Name: {0}'}" VerticalAlignment="Top" Margin="0,120,0,0"/>
<TextBlock Text="{Binding Path=Mark, StringFormat='Rating: {0}' }" VerticalAlignment="Top" Margin="0 180 0 0" />
<TextBlock Text="{Binding Path=Model, StringFormat='Category: {0}'}" VerticalAlignment="Top" Margin="0,200,0,0" />
<TextBlock Text="{Binding Path=Volume, StringFormat='Price: {0}'}" VerticalAlignment="Top" Margin="0,160,0,0" />
<TextBlock Text="{Binding Path=DateOfIssue, StringFormat='Description: {0}'}" VerticalAlignment="Top" Margin="0,140,0,0" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</StackPanel>
</UserControl>
And it the MainForm.xaml I added ProductPage.xaml
<ContentControl Margin="10" Grid.Column="1" Content="{Binding CurrentPage}"/>
The problem is that nothing is being outputted, I think it might be due to the loss of context. How do I properly pass the context to UserControl (ProductPage.xaml)?
ProductPage.xaml
Update:
I set DataContext for MainWindow
And DataContext working becouse {Binding CurrentPage} is working, but binding on Cars field is not working
Add this to the Window.Resources. Of course DataContext must be set for the MainForm.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type xmlnsForVewModel:MainViewModel}">
<ProductPage/>
</DataTemplate>
</Window.Resources>
</Window>
and if for ProductPage HomeViewModel supposed to be a DataContext, then HomeViewModel has to have Cars property, not MainViewModel.
1.find the MainForm.xaml.cs
2.find ctor
public MainForm()
{
InitializeComponent();
//set the MainForm DataContext
DataContext = new MainViewModel();
}
3.you should add ContentControl.DataTemplate to show your HomeViewModel
----20210427 update
Here demo i make, project name is WpfApp4
MainWindow.xaml
<Window x:Class="WpfApp4.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:WpfApp4"
xmlns:views="clr-namespace:WpfApp4.Views"
xmlns:viewModels="clr-namespace:WpfApp4.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewModels:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding ItemsSource, Mode=OneWay}" SelectedItem="{Binding SelectedCarViewModel, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<ContentControl Content="{Binding SelectedCarViewModel}">
<ContentControl.ContentTemplate>
<!--Style your product page here-->
<DataTemplate DataType="{x:Type viewModels:CarViewModel}">
<UniformGrid Columns="2">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Volume"/>
<TextBlock Text="{Binding Volume}"/>
<TextBlock Text="Price"/>
<TextBlock Text="{Binding Price}"/>
</UniformGrid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainWindowViewModel.cs
using System.Collections.ObjectModel;
namespace WpfApp4.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<CarViewModel> ItemsSource { get; } = new ObservableCollection<CarViewModel>();
private CarViewModel selectedCarViewModel;
public CarViewModel SelectedCarViewModel
{
get { return this.selectedCarViewModel; }
set { SetProperty(ref this.selectedCarViewModel, value); }
}
public MainWindowViewModel()
{
ItemsSource.Add(new CarViewModel() { Name = "BMW", Volume = 10, Price = 100 });
ItemsSource.Add(new CarViewModel() { Name = "Toyota", Volume = 5, Price = 80 });
ItemsSource.Add(new CarViewModel() { Name = "Benz", Volume = 20, Price = 150 });
// don't let view binding null value, so here initialze property "SelectedCarViewModel"
this.selectedCarViewModel = ItemsSource[0];
}
}
}
CarViewModel.cs
namespace WpfApp4.ViewModels
{
class CarViewModel : ViewModelBase
{
private string name;
public string Name
{
get { return this.name; }
set { SetProperty(ref this.name, value); }
}
private float volume;
public float Volume
{
get { return this.volume; }
set { SetProperty(ref this.volume, value); }
}
private decimal price;
public decimal Price
{
get { return this.price; }
set { SetProperty(ref this.price, value); }
}
}
}
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
namespace WpfApp4.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T t, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(t, value))
{
return false;
}
else
{
t = value;
RaisePropertyChanged(propertyName);
return true;
}
}
}
}
I have a ListView with a list of name and I want to be able to rename each value by double click or with a button.
I already did this for the doubleclick and it's working using this :
WPF
<ListView Grid.Row="0" x:Name="ListProfileView"
ItemsSource="{Binding ProfilesCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsReadOnly="True" VerticalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.True}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
c# (MVVM model with ICommand):
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox txtBox) || !(values[1] is bool value)) return;
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
}
}));
}
}
But for the button, I don't know how to get the path to the textbox to use the same command.
I tried things like that :
<Button Grid.Column="3" Content="{x:Static dictionnaries:ColorConfigurationDictionnary.rename}"
FontWeight="SemiBold"
Command="{Binding RenameCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding ElementName="ListProfileView" Path="ItemContainerGenerator"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
But I'm out of idea... Is that possible ?
It seems that there is some sort of misinformation going about so let me describe how MvvM works in the best way I can think of.
Model is where you store your data so let's call that a profile:
namespace Model
{
public class Profile
{
public string Name { get; set; }
}
}
Now what you need is a ViewModel which will provide Information which is manipulated data:
using VM.Commands;
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
ProfilesCollection = new List<Profile>();
for (int i = 0; i < 100; i++)
{
ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
}
RenameCommand = new TestCommand(renameCommandMethod, (o) => true);
}
void renameCommandMethod(object parameter)// to manipulate the colleciton you use the Commands which you already do but without the need for converters or any UI elements. Makes it much easier to handle.
{
string renameTo = parameter.ToString();
foreach (var profile in ProfilesCollection)
{
profile.Name = renameTo;
}
}
private List<Profile> profilesCollection;
public List<Profile> ProfilesCollection
{
get { return profilesCollection; }
set { profilesCollection = value; OnPropertyChanged(); }
}
private ICommand renameCommand;
public ICommand RenameCommand
{
get { return renameCommand; }
set { renameCommand = value; }
}
And the implementation of the RelayCommand:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace VM.Commands
{
public class TestCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public TestCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#region Implementation of ICommand
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
_execute?.Invoke(parameter);
}
public event EventHandler CanExecuteChanged;
#endregion
}
}
Then UI looks like this:
<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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:converter="clr-namespace:SO_app.Converters"
xmlns:validation="clr-namespace:SO_app.Validation"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:SO_app"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:model="clr-namespace:Model;assembly=Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<Window.Resources>
<CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
</VisualBrush.Visual>
</VisualBrush>
</Window.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Source={StaticResource profiles}}"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<ToolTip x:Key="Tip">
<TextBlock>
<Run>Some text here</Run>
<LineBreak/>
<Run Text="{Binding Name, StringFormat='Actual Text: {0}'}"/>
</TextBlock>
</ToolTip>
</DataTemplate.Resources>
<TextBlock Text="{Binding Name}" ToolTip="{StaticResource Tip}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Column="1">
<Button Content="Rename" Command="{Binding RenameCommand}" CommandParameter="NewName"></Button>
</StackPanel>
</Grid>
What this gives you is:
* Clean UI without any converters
* Every operation is done in the ViewModel without passing any UI elements.
* In UI you would do stuff like Styles with animation or setting font for text elements. But avoid handling clicks there. It is possible and sometimes it can't be avoided but try to utilise your ViewModel to manipulate the data.
BTW there are no controllers in here.
If you have any questions just ask.
Here is what I did:
that let me change the name of one value only by disabling the readonly of the textboxes in the list view.
I wrote that in the GUI code behind.
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox || values[0] is SetConfiguration) || !(values[1] is bool value)) return;
if (values[0] is TextBox txtBox)
{
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
txtBox.SelectAll();
}
}
if (values[0] is SetConfiguration config)
{
var listView = ListProfileView.ItemContainerGenerator.ContainerFromItem(config) as ListViewItem;
var presenter = FindVisualChild<ContentPresenter>(listView);
if(!(presenter.ContentTemplate.FindName("ProfileName", presenter) is TextBox txtBoxItem)) return;
if (!value)
{
txtBoxItem.Focus();
txtBoxItem.SelectAll();
}
txtBoxItem.IsReadOnly = value;
}
}));
}
}
private static TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is TChildItem item)
return item;
var childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
return childOfChild;
}
return null;
}
Hei,
I'm creating simple application with MVVM and stumbled on a problem which i find hard to resolve. On my application i have datagrid and couple of controls to edit currently selected item in datagrid. In my ViewModel i have CurrentSequence property what holds ColorSettingsSequencesSequence object (collection of these objects are used as DataContext for datagrid).
Here's xaml:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=ColorSettingsSequences}"
SelectedItem="{Binding Path=CurrentSequence, Mode=TwoWay}">
.... more things here ...
</DataGrid>
<StackPanel Grid.Column="0" Grid.Row="0">
<Grid>
<Label Content="Start temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqStartTemp" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,28,10,0" x:Name="tbSeqStartTemp" VerticalAlignment="Top" Text="{Binding Path=CurrentSequence.StartTemp}" />
</Grid>
<Grid>
<Label Content="Start color" Height="28" HorizontalAlignment="Left" x:Name="lblSeqHue" VerticalAlignment="Top" />
<xctk:ColorPicker Margin="0,28,10,0" x:Name="clrpSeqHue" SelectedColor="{Binding Path=CurrentSequence.StartHue, Converter={StaticResource hueToColor}, ConverterParameter=False}" ShowStandardColors="False" />
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0">
<Grid>
<Label Content="End temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndTemp" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,28,10,0" x:Name="tbSeqEndTemp" VerticalAlignment="Top" Text="{Binding Path=CurrentSequence.EndTemp}" />
</Grid>
<Grid>
<Label Content="End color" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndHue" VerticalAlignment="Top" />
<xctk:ColorPicker Margin="0,28,10,0" x:Name="clrpSeqEndHue" SelectedColor="{Binding Path=CurrentSequence.EndHue, Converter={StaticResource hueToColor}, ConverterParameter=False}" ShowStandardColors="False" />
</Grid>
</StackPanel>
Code:
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this._currentSequence;
}
set
{
this._currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
That works nicely, but the problem comes when i want to add validation. I would like to validate StartTemp and EndTemp separately and give different errors. How would i break up the ColorSettingsSequencesSequence object so that the bindings would also still work eq if i edit one value it gets updated in the datagrid also?
Here's what i tried, i created 2 new properties and added my validation to those:
private String _currentSequenceStartTemp;
public String CurrentSequenceStartTemp
{
get
{
return _currentSequenceStartTemp;
}
set
{
this._currentSequenceStartTemp = value;
CurrentSequence.StartTemp = value;
RaisePropertyChanged("CurrentSequenceStartTemp");
Validator.Validate(() => CurrentSequenceStartTemp);
ValidateCommand.Execute(null);
}
}
private String _currentSequenceEndTemp;
public String CurrentSequenceEndTemp
{
get
{
return _currentSequenceEndTemp;
}
set
{
this._currentSequenceEndTemp = value;
CurrentSequence.EndTemp = value;
RaisePropertyChanged("CurrentSequenceEndTemp");
Validator.Validate(() => CurrentSequenceEndTemp);
ValidateCommand.Execute(null);
}
}
And the i just binded TextBoxes to those values, instead of binding them straight to CurrentSequence. I also added setting the CurrentSequence values in the setters and hoped that way my changes will be pushed all the way back to original collection and will be changed in datagrid. That didn't happen..
When CurrentSequence is changed i change values of these properties also:
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this._currentSequence;
}
set
{
this._currentSequence = value;
RaisePropertyChanged("CurrentSequence");
if (value != null)
{
CurrentSequenceStartTemp = value.StartTemp;
CurrentSequenceEndTemp = value.EndTemp;
}
else
{
CurrentSequenceStartTemp = String.Empty;
CurrentSequenceEndTemp = String.Empty;
}
}
}
I have reproduced your problem. But I couldn't find any problem. Everything works fine.
Validate StartTemp and EndTemp separately.
If one value is updated, the datagrid should also be updated
So I have solved above two problems in my project.
The Results
After changing start temperature to 40, the datagrid value also has been changed.
Let's create an error in start temperature text box.
And now the other one
You can see now both the properties are validated separately.
This is the project I have created.
Project Structure
ViewModelBase class
public class ViewModelBase : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
#endregion
}
ColorSettingsSequencesSequence class
public class ColorSettingsSequencesSequence : ViewModelBase, IDataErrorInfo
{
#region Declarations
private string startColor;
private string startTemperature;
private string endTemperature;
#endregion
#region Properties
/// <summary>
/// Gets or sets the start color.
/// </summary>
/// <value>
/// The start color.
/// </value>
public string StartColor
{
get
{
return this.startColor;
}
set
{
this.startColor = value;
OnPropertyChanged("StartColor");
}
}
/// <summary>
/// Gets or sets the start temperature.
/// </summary>
/// <value>
/// The start temperature.
/// </value>
public string StartTemperature
{
get
{
return this.startTemperature;
}
set
{
this.startTemperature = value;
OnPropertyChanged("StartTemperature");
}
}
/// <summary>
/// Gets or sets the end temperature.
/// </summary>
/// <value>
/// The end temperature.
/// </value>
public string EndTemperature
{
get
{
return this.endTemperature;
}
set
{
this.endTemperature = value;
OnPropertyChanged("EndTemperature");
}
}
#endregion
/// <summary>
/// Gets an error message indicating what is wrong with this object.
/// </summary>
/// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
public string Error
{
get
{
return "";
}
}
/// <summary>
/// Gets the error message for the property with the given name.
/// </summary>
/// <param name="columnName">Name of the column.</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
if (columnName.Equals("StartTemperature"))
{
if (string.IsNullOrEmpty(this.StartTemperature))
{
return "Please enter a start temperature";
}
}
if (columnName.Equals("EndTemperature"))
{
if (string.IsNullOrEmpty(this.EndTemperature))
{
return "Please enter a end temperature";
}
}
return "";
}
}
}
MainViewModel
public class MainViewModel : ViewModelBase
{
#region Declarations
private ColorSettingsSequencesSequence currentSequence;
private ObservableCollection<ColorSettingsSequencesSequence> colorSettingsSequences;
#endregion
#region Properties
/// <summary>
/// Gets or sets the current sequence.
/// </summary>
/// <value>
/// The current sequence.
/// </value>
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this.currentSequence;
}
set
{
this.currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
/// <summary>
/// Gets or sets the color settings sequences.
/// </summary>
/// <value>
/// The color settings sequences.
/// </value>
public ObservableCollection<ColorSettingsSequencesSequence> ColorSettingsSequences
{
get
{
return this.colorSettingsSequences;
}
set
{
this.colorSettingsSequences = value;
OnPropertyChanged("ColorSettingsSequences");
}
}
#endregion
#region Commands
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainViewModel" /> class.
/// </summary>
public MainViewModel()
{
this.ColorSettingsSequences = new ObservableCollection<ColorSettingsSequencesSequence>();
ColorSettingsSequencesSequence sequence1 = new ColorSettingsSequencesSequence();
sequence1.StartColor = "Blue";
sequence1.StartTemperature = "10";
sequence1.EndTemperature = "50";
ColorSettingsSequences.Add(sequence1);
ColorSettingsSequencesSequence sequence2 = new ColorSettingsSequencesSequence();
sequence2.StartColor = "Red";
sequence2.StartTemperature = "20";
sequence2.EndTemperature = "60";
ColorSettingsSequences.Add(sequence2);
ColorSettingsSequencesSequence sequence3 = new ColorSettingsSequencesSequence();
sequence3.StartColor = "Yellow";
sequence3.StartTemperature = "30";
sequence3.EndTemperature = "70";
ColorSettingsSequences.Add(sequence3);
this.CurrentSequence = sequence1;
}
#endregion
#region Private Methods
#endregion
}
MainWindow.xaml (XAML)
<Window x:Class="DataGridValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="149" />
<RowDefinition Height="73" />
<RowDefinition Height="123" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="249*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding ColorSettingsSequences}"
SelectedItem="{Binding CurrentSequence}"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Start Color" Binding="{Binding StartColor}" />
<DataGridTextColumn Header="End Color" Binding="{Binding StartTemperature}" />
<DataGridTextColumn Header="End Color" Binding="{Binding EndTemperature}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="0" Grid.Row="1">
<Grid>
<Label Content="Start temperature (°C)"
Height="28"
HorizontalAlignment="Left"
x:Name="lblSeqStartTemp"
VerticalAlignment="Top" />
<TextBox Height="23"
Margin="10,28,10,0"
x:Name="tbSeqStartTemp"
VerticalAlignment="Top"
Text="{Binding Path=CurrentSequence.StartTemperature, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</Grid>
</StackPanel>
<StackPanel Grid.Row="2" Margin="0,0,0,43">
<Grid>
<Label Content="End temperature (°C)"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<TextBox Height="23"
Margin="10,28,10,0"
x:Name="tbSeqEndTemp"
VerticalAlignment="Top"
Text="{Binding Path=CurrentSequence.EndTemperature, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</Grid>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs (Code behind file)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
mainGrid.DataContext = new MainViewModel();
}
}
If I have understood correctly, your problem is that you want to commit your property value even if the validation fails. In case I am wrong in this assumption, the solution is even easier, basically what sine hinted at in his comment, that you would only need to implement INotifyPropertyChanged in your ColorSettingsSequencesSequence class.
I couldn't infer from your post what kind of validation you employ, but here is how I'd do it. The key to updating your datagrid even if validation in the textbox fails, is the ValidationStep="UpdatedValue" part of the ValidationRule (and the implementation of the rule).
View:
<UserControl x:Class="WpfApplication1.DemoValidation"
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:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:DemoValidationViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="ph" />
</Border>
<Border BorderBrush="LightGray" BorderThickness="1" Background="Beige">
<TextBlock Foreground="Red" FontSize="12" Margin="5"
Text="{Binding ElementName=ph, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<Label Content="Start temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqStartTemp" VerticalAlignment="Top" />
<TextBox Height="23" x:Name="tbSeqStartTemp" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="CurrentSequence.StartTemp" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:TempValidationRule MaximumTemp="400" MinimumTemp="-100" ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<StackPanel Grid.Column="2" Grid.Row="0">
<Label Content="End temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndTemp" VerticalAlignment="Top" />
<TextBox Height="23" x:Name="tbSeqEndTemp" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="CurrentSequence.EndTemp" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:TempValidationRule MaximumTemp="500" MinimumTemp="100" ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<DataGrid Grid.Row="2" Grid.ColumnSpan="3" Margin="0,10,0,0"
ItemsSource="{Binding Path=ColorSettingsSequences}"
SelectedItem="{Binding Path=CurrentSequence, Mode=TwoWay}" />
</Grid>
</UserControl>
ViewModel:
public class DemoValidationViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get { return this._currentSequence; }
set
{
this._currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
public List<ColorSettingsSequencesSequence> ColorSettingsSequences { get; private set; }
public DemoValidationViewModel()
{
// dummy data
this.ColorSettingsSequences = new List<ColorSettingsSequencesSequence>()
{
new ColorSettingsSequencesSequence() { StartTemp = "10", EndTemp = "20" },
new ColorSettingsSequencesSequence() { StartTemp = "20", EndTemp = "30" },
new ColorSettingsSequencesSequence() { StartTemp = "30", EndTemp = "40" }
};
}
}
ColorSettingsSequencesSequence:
public class ColorSettingsSequencesSequence : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _startTemp;
public string StartTemp { get { return _startTemp; } set { _startTemp = value; OnPropertyChanged("StartTemp");}}
private string _endTemp;
public string EndTemp { get { return _endTemp; } set { _endTemp = value; OnPropertyChanged("EndTemp"); } }
}
ValidationRule (see also this thread):
public class TempValidationRule : ValidationRule
{
// default values
private int _minimumTemp = -273;
private int _maximumTemp = 2500;
public int MinimumTemp
{
get { return _minimumTemp; }
set { _minimumTemp = value; }
}
public int MaximumTemp
{
get { return _maximumTemp; }
set { _maximumTemp = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string error = null;
string s = GetBoundValue(value) as string;
if (!string.IsNullOrEmpty(s))
{
int temp;
if (!int.TryParse(s, out temp))
error = "No valid integer";
else if (temp > this.MaximumTemp)
error = string.Format("Temperature too high. Maximum is {0}.", this.MaximumTemp);
else if (temp < this.MinimumTemp)
error = string.Format("Temperature too low. Minimum is {0}.", this.MinimumTemp);
}
return new ValidationResult(string.IsNullOrEmpty(error), error);
}
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);
// Extract the value of the property
object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);
return propertyValue;
}
else
{
return value;
}
}
}
Suppose you have a View with multiple Texboxes like this
<TextBox Text="{Binding myText1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
each already containing some text. If the user changes this text the Textbox Border should change to Orange and if he undos his changes it should get it's default color.
At the moment I do it like this
<TextBox Height="23" Text="{Binding myText1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" BorderThickness="2">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding myDirtyText1, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Is there a more generic / simpler way to do this?
Edit
I am already using IDataErrorInfo + System.ComponentModel.DataAnnotations for Error validation. Maybe there is a similar way in this case but I didn't found anything useful to reduce my xaml and code to an minimum.
EDIT 2.0
i think you doesn't really understand my problem so i will provide a better sample of how it actual looks like:
View Xaml (no codebehind)
<Grid Margin="12">
<Label Content="Name:" Height="28" HorizontalAlignment="Left" VerticalAlignment="Top" Width="79" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,2,0,0" VerticalAlignment="Top" Width="170" BorderThickness="2"
Text="{Binding NameD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding dirtyName, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Label Content="Anzeigetext:" Height="28" HorizontalAlignment="Left" Margin="0,34,0,0" VerticalAlignment="Top" Width="79" />
<TextBox BorderThickness="2" Height="23" HorizontalAlignment="Left" Margin="102,36,0,0" VerticalAlignment="Top" Width="170"
Text="{Binding AnzeigetextD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding dirtyAnzeigetext, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Label Content="Preis:" Height="28" HorizontalAlignment="Left" Margin="0,68,0,0" VerticalAlignment="Top" Width="79" />
<TextBox BorderThickness="2" Height="23" HorizontalAlignment="Left" Margin="102,70,0,0" VerticalAlignment="Top" Width="170"
Text="{Binding PreisD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat=\{0:c\}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding dirtyPreis, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Content="Speichern" Height="23" HorizontalAlignment="Left" Margin="102,110,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SaveCommand}"/>
<Button Content="Abbrechen" Height="23" HorizontalAlignment="Left" Margin="197,110,0,0" VerticalAlignment="Top" Width="75" Command="{Binding CancelCommand}"/>
</Grid>
ViewModel
public class MenuangebotVM : DetailVM, IContains
{
#region private Values
private Menuangebot myOriginal = new Menuangebot();
private Menuangebot myValue = new Menuangebot();
#endregion // private Values
#region Properties
#region Detail Properties
public int Id { get { return myOriginal.Id; } }
public bool? Result { get; private set; }
public string Beschreibung { get { return "Einrichtung"; } }
[Required]
[RegularExpression(#"^[0-9a-zA-ZäöüßÄÖÜß''-'\s]{2,40}$")]
public string NameD
{
get { return myValue.Name; }
set
{
myValue.Name = value;
RaisePropertyChanged(() => Reg(() => NameD));
RaisePropertyChanged(() => Reg(() => dirtyName));
}
}
public bool dirtyName
{
get { return (!isNew && myValue.Name != myOriginal.Name) ? true : false; }
}
[Required]
[RegularExpression(#"^[0-9a-zA-ZäöüßÄÖÜß''-'\s]{2,25}$")]
public string AnzeigetextD
{
get { return myValue.Anzeigetext; }
set
{
myValue.Anzeigetext = value;
RaisePropertyChanged(() => Reg(() => AnzeigetextD));
RaisePropertyChanged(() => Reg(() => dirtyAnzeigetext));
}
}
public bool dirtyAnzeigetext
{
get { return (!isNew && myValue.Anzeigetext != myOriginal.Anzeigetext) ? true : false; }
}
[Required]
public decimal PreisD
{
get { return myValue.Preis; }
set
{
myValue.Preis = value;
RaisePropertyChanged(() => Reg(() => PreisD));
RaisePropertyChanged(() => Reg(() => dirtyPreis));
}
}
public bool dirtyPreis
{
get
{
var value = myValue.Preis;
var Original = myOriginal.Preis;
return (!isNew && value != Original) ? true : false;
}
}
#endregion //Detail Properties
#endregion //Properties
// more code
}
what i excspect should be something like
View
<Grid Margin="12">
<Label Content="Name:" Height="28" HorizontalAlignment="Left" VerticalAlignment="Top" Width="79" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,2,0,0" VerticalAlignment="Top" Width="170" BorderThickness="2"
Text="{Binding NameD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, EditesOnDataChanges=true}">
</TextBox>
<Label Content="Anzeigetext:" Height="28" HorizontalAlignment="Left" Margin="0,34,0,0" VerticalAlignment="Top" Width="79" />
<TextBox BorderThickness="2" Height="23" HorizontalAlignment="Left" Margin="102,36,0,0" VerticalAlignment="Top" Width="170"
Text="{Binding AnzeigetextD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, EditesOnDataChanges=true}">
</TextBox>
<Label Content="Preis:" Height="28" HorizontalAlignment="Left" Margin="0,68,0,0" VerticalAlignment="Top" Width="79" />
<TextBox BorderThickness="2" Height="23" HorizontalAlignment="Left" Margin="102,70,0,0" VerticalAlignment="Top" Width="170"
Text="{Binding PreisD, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat=\{0:c\, EditesOnDataChanges=true}}">
</TextBox>
<Button Content="Speichern" Height="23" HorizontalAlignment="Left" Margin="102,110,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SaveCommand}"/>
<Button Content="Abbrechen" Height="23" HorizontalAlignment="Left" Margin="197,110,0,0" VerticalAlignment="Top" Width="75" Command="{Binding CancelCommand}"/>
</Grid>
ViewModel
public class MenuangebotVM : DetailVM, IContains
{
#region private Values
private Menuangebot myOriginal = new Menuangebot();
private Menuangebot myValue = new Menuangebot();
#endregion // private Values
#region Properties
#region Detail Properties
public int Id { get { return myOriginal.Id; } }
public bool? Result { get; private set; }
public string Beschreibung { get { return "Einrichtung"; } }
[Required]
[RegularExpression(#"^[0-9a-zA-ZäöüßÄÖÜß''-'\s]{2,40}$")]
[Default(myOriginal.Name)] //<-- added
public string NameD
{
get { return myValue.Name; }
set
{
myValue.Name = value;
RaisePropertyChanged(() => Reg(() => NameD));
}
}
[Required]
[RegularExpression(#"^[0-9a-zA-ZäöüßÄÖÜß''-'\s]{2,25}$")]
[Default(myOriginal.Anzeigetext)] //<-- added
public string AnzeigetextD
{
get { return myValue.Anzeigetext; }
set
{
myValue.Anzeigetext = value;
RaisePropertyChanged(() => Reg(() => AnzeigetextD));
}
}
[Required]
[Default(myOriginal.Preis)] //<-- added
public decimal PreisD
{
get { return myValue.Preis; }
set
{
myValue.Preis = value;
RaisePropertyChanged(() => Reg(() => PreisD));
}
}
#endregion //Detail Properties
#endregion //Properties
// more code
}
public class ViewModel:INotifyPropertyChanged
{
private string initialText;
public ViewModel()
{
Text = "ABCD";
initialText = Text;
DefaultBorder = true;
}
private string text;
public string Text
{
get { return text; }
set { text = value;
if (value == initialText)
DefaultBorder = true;
else
DefaultBorder = false;
Notify("Text"); }
}
private bool defaultBorder;
public bool DefaultBorder
{
get { return defaultBorder; }
set { defaultBorder = value; Notify("DefaultBorder"); }
}
private void Notify(string propertyName)
{
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is bool && !(bool)value)
return new SolidColorBrush(Colors.Orange);
else
return new SolidColorBrush(Colors.Navy); //Or default whatever you want
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<local:MyConverter x:Key="MyConverter"/>
</Window.Resources>
<Grid>
<TextBox BorderThickness="4" BorderBrush="{Binding DefaultBorder, Converter={StaticResource MyConverter}}" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Here I have property Text in ViewModel that is bound to TextBox and at the beginning i preserved the initial Text value . and then whenever user type i compare it in setter of Text property and set Bool Property accordingly , this bool property will specify which color to Bind using converter.Ignore minor issues hope you will get an idea.
You can probably turn this to a custom control/UserControl and add a IsDirtyDependencyProperty, and the IsDirtyColorDependencyProperty (or attached dependency property). That way you replace all your textboxes with this and not have to repeat the code over and over.
Coincidentally I just had a problem almost identical to yours, and I solved it by wrapping the TextBox in a Border. Furthermore, this also solved a problem whereby BorderBrush colours cannot be changed on Windows 8 machines.
I would thus recommend this approach. The code is quite straightforward, whereby you just add your textBox to the Border and change the border's BorderBrush property.
You can use Attached behavior for that
public static class TextChangedAttachedBehavior
{
public static bool GetChanged(DependencyObject obj)
{
return (bool)obj.GetValue(ChangedProperty);
}
public static void SetChanged(DependencyObject obj, string value)
{
obj.SetValue(ChangedProperty, value);
}
public static readonly DependencyProperty ChangedProperty =
DependencyProperty.RegisterAttached("Changed", typeof(bool),
typeof(TextChangedAttachedBehavior), new PropertyMetadata(false, HookupBehavior));
private static void HookupBehavior(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null)
return;
textBox.TextChanged += TextBoxOnTextChanged;
}
private static void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
var textBox = sender as TextBox;
if (textBox == null)
return;
textBox.BorderBrush = new SolidColorBrush(Colors.Orange);
}
}
And than in xaml
<TextBox Text="{Binding myText1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TextChangedAttachedBehavior.Changed = "True" />
In this program, I'm able to add one tab at a time by clicking the "Add Course" button. Ideally, the header of the tab should be the course name I entered and the text in the textbox , which is on the tab, should display the course name.
However, it's not functioning correctly. When I tried to add more than 1 tabs, each time it gives me this error message:
System.Windows.Data Error: 40 : BindingExpression path error: 'Text' property not found on 'object' ''MyHomeworkViewModel' (HashCode=33010577)'. BindingExpression:Path=Text; DataItem='MyHomeworkViewModel' (HashCode=33010577); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Also, it seems to "override" other tab's text (just text, not header). For example, if I add a tab with header "a", the text of that is also "a". Then if I add "B", both textboxes on two tabs become "B". However, if I print out the Text property of each tab (MyHomeworkModel in this case), they are "a" and "B", respectively.
I have been debugging this whole day but no luck. Any help would be appreciated!
My View (DataContext set to MyHomeworkViewModel):
<Window x:Class="MyHomework__MVVM_.MyHomeworkView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="My Homework" Height="450" Width="800" ResizeMode="CanMinimize">
<Grid Margin="0,0,10,10">
<TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="764" Margin="10,10,0,0" ItemsSource="{Binding AllTabs}" SelectedItem="{Binding SelectedTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Content">
<Setter.Value>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="20"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<Button Content="Add Course" HorizontalAlignment="Left" VerticalAlignment="Top" Width="105" Margin="10,351,0,0" Height="50" Command="{Binding AddCourseCommand}"/>
<Button Content="Drop Course" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76" Margin="138,379,0,0" Height="22" Command="{Binding DropCourseCommand, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Save HW" HorizontalAlignment="Left" VerticalAlignment="Top" Width="105" Margin="669,351,0,0" Height="50"/>
</Grid>
</Window>
My Model:
using System.ComponentModel;
namespace MyHomework__MVVM_
{
class MyHomeworkModel : INotifyPropertyChanged
{
private string header, text;
public event PropertyChangedEventHandler PropertyChanged;
public string Header
{
get
{
return header;
}
set
{
header = value;
OnPropertyChanged("Header");
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
My ViewModel:
using MyHomework;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace MyHomework__MVVM_
{
class MyHomeworkViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyHomeworkModel> allTabs;
private MyHomeworkModel selectedTab;
private MyHomeworkView mainWindow;
public event PropertyChangedEventHandler PropertyChanged;
public MyHomeworkViewModel(MyHomeworkView mainWindow)
{
allTabs = new ObservableCollection<MyHomeworkModel>();
this.mainWindow = mainWindow;
AddCourseCommand = new AddCourseCommand(this);
DropCourseCommand = new DropCourseCommand(this);
}
public ObservableCollection<MyHomeworkModel> AllTabs
{
get
{
return allTabs;
}
set
{
allTabs = value;
OnPropertyChanged("AllTabs");
}
}
public MyHomeworkModel SelectedTab
{
get
{
return selectedTab;
}
set
{
selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public ICommand AddCourseCommand
{
get;
private set;
}
public ICommand DropCourseCommand
{
get;
private set;
}
public void AddNewTab()
{
NewCourseName ncn = new NewCourseName();
ncn.Owner = mainWindow;
ncn.ShowDialog();
if (ncn.courseName != null)
{
MyHomeworkModel newTab = new MyHomeworkModel();
newTab.Header = ncn.courseName;
newTab.Text = ncn.courseName;
AllTabs.Add(newTab);
SelectedTab = newTab;
}
foreach (MyHomeworkModel item in AllTabs)
{
Console.WriteLine(item.Text);
}
}
public bool CanDrop()
{
return SelectedTab != null;
}
public void RemoveTab()
{
DropCourseConfirmation dcc = new DropCourseConfirmation();
dcc.Owner = mainWindow;
dcc.ShowDialog();
if (dcc.drop == true)
{
int index = AllTabs.IndexOf(SelectedTab);
AllTabs.Remove(SelectedTab);
if (AllTabs.Count > 0)
{
if (index == 0)
{
SelectedTab = AllTabs[0];
}
else
{
SelectedTab = AllTabs[--index];
}
}
else
{
SelectedTab = null;
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Please let me know if you need more codes to help me.
Change your
<Setter Property="Content">
<Setter.Value>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</Setter.Value>
</Setter>
for this:
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
Your DataContext isn't what you think it is. Read the error there. It states that "Text" is not a valid property on MyHomeworkViewModel which is true (As opposed to your MyHomeworkModel).
What you need to be modifying instead of the ItemContainerStyle is instead the ItemTemplate and the ContentTemplate which uses the appropriate object within your ItemsSource as its DataContext.
Additionally, the binding in your TextBox needs to be Text="{Binding Text, Mode=TwoWay}" or it won't modify the property in your model.