I´m new with WPF and the MVVM pattern, i have a view with 2 ComboBox, 1 Datapicker, 1 textbox and 1button, the intention is when i hit the button i obtain the data from this view in the ViewModel, to try this i've been based on this question in StackOverflow: https://stackoverflow.com/questions/27447042/xaml-button-comand-to-pass-to-date-picker-properties-to-method
The problema is that in the XAML in
Window.Resources PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
Give me an error, NameSpace prefix "PedidosRetraso" not defined, and i don´t know why, the name of the namespace is correct,¿What i´m doing wrong?.
Also I would like to know how i can verify that the ComboBox have some value selected to disable the button in case of not selected value, i think i shoul do it in CanExecute but i dont know how i know the value of the ComboBox.
This is my XAML code
<Window x:Class="PedidosRetraso.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:PedidosRetraso"
mc:Ignorable="d"
Title="MainWindow" Height="700" Width="700"
>
<Window.Resources>
<PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
</Window.Resources>
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="22,59,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="comboBox_SelectionChanged">
</ComboBox>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="573,66,0,0" VerticalAlignment="Top" Width="75" >
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource ResourceKey=multiDateConverter}">
<Binding ElementName="textBox" Path="Text"></Binding>
<Binding ElementName="comboBox" Path="Text"></Binding>
<Binding ElementName="comboBox1" Path="Text"></Binding>
<Binding ElementName="Fecha" Path="Text"></Binding>
</MultiBinding>
</Button.CommandParameter>
<Button.Command>
<Binding Path="GetAllActionLogsBetweenDatesCommand"></Binding>
</Button.Command>
</Button>
<ComboBox x:Name="comboBox1" HorizontalAlignment="Left" Margin="183,59,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding _combo}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Situación: "></TextBlock>
<TextBlock Text="{Binding Path=Nombre}" Width="80"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And this is my ViewModel:
public class RelayCommand : ICommand
{
private Predicate<object> m_canExecute;
private Action<object> m_execute;
public RelayCommand(Action<object> execute)
{
m_execute = execute;
}
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
m_canExecute = CanExecute;
m_execute = execute;
}
public bool CanExecute(object parameter)
{
if (m_canExecute == null)
{
return true;
}
return m_canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (m_execute != null)
{
m_execute(parameter);
}
}
}
public class ICommandMultiDateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new string[] { values[0].ToString(), values[1].ToString(), values[2].ToString(),values[3].ToString() };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
class ViewModelMain
{
public ICommand GetAllActionLogsBetweenDatesCommand { get; set; }
public ObservableCollection<PocoCombo> _combo { get; set; }
public ViewModelMain()
{
GetAllActionLogsBetweenDatesCommand = new RelayCommand(GetAllActionLogsBetweenDates_Execute);
_combo = new ObservableCollection<PocoCombo> { new PocoCombo { Id = 20, Nombre = "Enviado" }, new PocoCombo { Id = 25, Nombre = "DIF" }, new PocoCombo { Id = 30, Nombre = "Confirmado" }, new PocoCombo { Id = 40, Nombre = "RP" }, new PocoCombo { Id = 50, Nombre = "Cerrado" }, new PocoCombo { Id = 60, Nombre = "C" } };
}
private void GetAllActionLogsBetweenDates_Execute(object parameter)
{
try
{
var stringList = parameter as string[];
string proveedor = stringList[0];
string empresa = stringList[1];
string situacion = stringList[2];
DateTime fecha = DateTime.Parse(stringList[3]);
// Aqui la consulta SQL
}
catch (Exception ex)
{
}
}
EDIT i put a photo
Thanks.
You have defined the namespace here xmlns:local="clr-namespace:PedidosRetraso", so use it!
Change
<PedidosRetraso:ICommandMultiDateConverter x:Key="multiDateConverter"/>
To
<local:ICommandMultiDateConverter x:Key="multiDateConverter"/>
EDIT
You have added a picture. ICommandMultiDataConverter isn't in namespace PedidosRetraso as I thought but PedidosRetraso.viewModel
Change xmlns:local="clr-namespace:PedidosRetraso" to xmlns:local="clr-namespace:PedidosRetraso.viewModel"
Related
I have two button commands. Let's call them BrowseButton1 and LoadButton1. The first command browses a file path (MVVM object) and sets this to a local path of my machine. Then the second ICommand (a.k.a LoadButton1) uses that file path (same MVVM object) to load some data to an SQL table.
My problem is that I cannot use the value of the file path in the second ICommand, because it is returned null
XAML code
<Window x:Class="TestEnvironment.MainWIndowTestStackOverflow"
x:Name="MainWindowTestStackOverflow"
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:TestEnvironment"
mc:Ignorable="d"
Height="720"
Width="1145"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
BorderBrush="Black"
BorderThickness="1.5,1.5,1.5,1.5"
WindowStyle="None">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid x:Name="GridMain"
Width="1145"
Background="White"
HorizontalAlignment="Center"
ShowGridLines="False"
Grid.Row="1">
<!--Grid Columns-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0"/>
<ColumnDefinition Width="195"/>
<ColumnDefinition Width="295"/>
<ColumnDefinition Width="650"/>
<ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions>
<!--Grid Rows-->
<Grid.RowDefinitions>
<RowDefinition Height="0"/>
<RowDefinition Height="45"/>
<RowDefinition Height="45"/>
<RowDefinition Height="45"/>
<RowDefinition Height="45"/>
<RowDefinition Height="52"/>
<RowDefinition Height="400"/>
</Grid.RowDefinitions>
<TextBox
Name="FileNameTextBox"
Text="{Binding Path=FilesFilePath}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="5,0,0,0"
IsReadOnly="True"
FontStyle="Italic"
FontFamily="Arial"
FontSize="9"
BorderThickness="0"
Grid.Column="2"
Grid.Row="1"/>
<!--Apply ICommand to browse file 1st time-->
<Button
x:Name="BrowseButton1"
Content="Browse"
Command="{Binding Path=BrowseButtonCommand}"
IsEnabled="{Binding Path=EnableFilesBrowseButton, Converter={StaticResource BooleanToVisibilityConverter}}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Width="80"
Height="25"
Margin="40,0,0,0"
Padding="0"
FontWeight="Light"
FontSize="10"
Grid.Column="3"
Grid.Row="1"
Cursor="Hand">
<Button.CommandParameter>
<MultiBinding>
<MultiBinding.Converter>
<local:BrowseButtonConverter/>
</MultiBinding.Converter>
<Binding Path="FilesFilePath"/> //this is the value I want to exchange between the two ICommands
<Binding Path="EnableFilesBrowseButton"/>
<Binding Path="EnableFilesLoadButton"/>
<Binding Path="EnableFilesViewButton"/>
<Binding Path="FilesPanelVisibility"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Button
x:Name="LoadButton1"
Content="Load"
Command="{Binding Path=LoadButtonCommand}"
IsEnabled="{Binding Path=EnableFilesLoadButton, Converter={StaticResource BooleanToVisibilityConverter}}"
Focusable="False"
Width="80"
Height="25"
Margin="135,0,0,0"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Grid.Column="3"
Grid.Row="1"
Cursor="Hand">
<Button.CommandParameter>
<MultiBinding>
<MultiBinding.Converter>
<local:LoadButtonConverter/>
</MultiBinding.Converter>
<Binding Path="FilesFilePath"/> //this returns null even though browse button return the FilesFilePath
</MultiBinding>
</Button.CommandParameter>
</Button>
</Grid>
</Window>
.cs file
namespace TestEnvironment
{
//Command parameters -Browse Button
public class BrowseButtonCommandParameters
{
public string FilePathSelected { get; set; } //parameter 1
public bool EnableBrowseButton { get; set; } //parameter 2
public bool EnableLoadButton { get; set; } //parameter 3
}
//Browse - MultiValueConverter
class BrowseButtonConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Error handling omitted for brevity
// Casting omitted because question's code has insufficient context
return new BrowseButtonCommandParameters
{
FilePathSelected = (string)values[0],
EnableBrowseButton = (bool)values[1],
EnableLoadButton = (bool)values[2],
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("to-source binding mode not supported");
}
private static BrowseButtonConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null) _converter = new BrowseButtonConverter();
return _converter;
}
public BrowseButtonConverter()
: base()
{
}
}
//Command parameters -Load Button
public class LoadButtonCommandParameters
{
public string FilePathSelected { get; set; } //parameter 1
}
//Load - MultiValueConverter
class LoadButtonConverter : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Error handling omitted for brevity
// Casting omitted because question's code has insufficient context
return new LoadButtonCommandParameters
{
FilePathSelected = (string)values[0], //this is actually the FilePath defined from Browse Command.
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("to-source binding mode not supported");
}
private static LoadButtonConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null) _converter = new LoadButtonConverter();
return _converter;
}
public LoadButtonConverter()
: base()
{
}
}
//----------------------------------------------------------------------------------------------------
public class MainWindowViewModel : INotifyPropertyChanged
{
//MVVM objects bound to XAML objects and connected to ICommand parameters
//Used by BrowseButton1
//1.
private bool _enableFilesLoadButton;
public bool EnableFilesLoadButton
{
get
{
return _enableFilesLoadButton;
}
set
{
_enableFilesLoadButton = value;
OnPropertyChanged("EnableFilesLoadButton");
}
}
//2.
private bool _enableFilesBrowseButton;
public bool EnableFilesBrowseButton
{
get
{
return _enableFilesBrowseButton;
}
set
{
_enableFilesBrowseButton = value;
OnPropertyChanged("EnableFilesBrowseButton");
}
}
//3.
private string _FilesFilePath;
public string FilesFilePath
{
get
{
return _FilesFilePath;
}
set
{
_FilesFilePath = value;
OnPropertyChanged("FilesFilePath");
}
}
//----------------------------------------------------------------------------------------------------
//ICommand: BrowseButtonCommand
public ICommand BrowseButtonCommand
{
get { return new DelegateCommand<object>(FuncBrowseCommand); }
}
public void FuncBrowseCommand(object parameters)
{
var param = (BrowseButtonCommandParameters)parameters;
Nullable<bool> browse_result = BrowseFile(param.FilePathSelected); //command parameter 1, FilesFilePath
Debug.WriteLine("FilesFilePath " + FilesFilePath);
//Load button gets instantly disabled when every time the user clicks the Browse Button
param.EnableLoadButton = false; //command parameter 2
Debug.WriteLine("EnableLoadButton: " + EnableFilesLoadButton);
//Browse file
if (browse_result == true)
{
param.EnableLoadButton = true; //command parameter 2
Debug.WriteLine("EnableLoadButton: " + EnableFilesLoadButton);
param.EnableBrowseButton = true; //command parameter 3
Debug.WriteLine("EnableBrowseButton: " + EnableFilesBrowseButton);
}
else
{
return;
}
}
public void FuncLoadButton(object parameters)
{
var param = (LoadButtonCommandParameters)parameters;
Debug.Writeline("FilePath: "+ param.FilePathSelected); //this returns null
}
//Browse function used in Browse ICommand
public bool BrowseFile(string filepathselected)
{
// Create OpenFileDialog
OpenFileDialog openFileDlg = new OpenFileDialog();
// Launch OpenFileDialog by calling ShowDialog method
Nullable<bool> result = openFileDlg.ShowDialog();
Debug.WriteLine("1. browse window result: " + result);
// Set filter for file extension and default file extension
openFileDlg.DefaultExt = ".csv";
openFileDlg.Filter = "All files (*.*)|*.*|CSV file (*.csv)|*.csv|Text files (*.txt)|*.txt";
// Set initial directory
openFileDlg.InitialDirectory = #"C:\Documents\";
openFileDlg.Title = "Browse Files";
openFileDlg.CheckFileExists = true;
openFileDlg.CheckPathExists = true;
openFileDlg.RestoreDirectory = true;
// Multiple selection with all file types
openFileDlg.Multiselect = true;
// Get the selected file name and display in a TextBox.
// Load content of file in a TextBlock
if (result == true)
{
filepathselected = openFileDlg.FileName;
Debug.WriteLine("2. File Path: " + filepathselected);
}
return (bool)result;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
Both code snippets will help you to reproduce my problem. The thing is that neither FilesFilePath command parameter can pass its value to Load Command, nor any of those changes in the DataContext can be viewed in MainWindow UI. Even though the Browse button gives value to parameter FilesFilePath, the UI cannot show this text. Recall that property .Text of FileNameTextBox is bound to FilesFilePath. So I want the UI to show that text also.
Summarizing I have two goals to achieve based on the snippet provided
Pass the value of FilesFilePath(string) between the two ICommands.
View the value of FilesFilePath(string) in UI after using the Browse ICommand.
I am more than willing to provide in the comments any additional info if something was unclear to you.
The reason why your code doesn't work is that you're never setting the FilesFilePath property.
In your BrowseFile method, you should set FilesFilePath if the result is true:
//Browse function used in Browse ICommand
public bool BrowseFile(string filepathselected)
{
...
// Get the selected file name and display in a TextBox.
// Load content of file in a TextBlock
if (result == true)
{
filepathselected = openFileDlg.FileName;
// Set the FilesFilePath property here!
FilesFilePath = filepathselected;
Debug.WriteLine("2. File Path: " + filepathselected);
}
return (bool)result;
}
I want to bind a list of buttons in combo boxes. Each Combobox will contains buttons of one category and so on. As referred in attached image.
Below is my code:
<ItemsControl x:Name="iNumbersList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Click="ItemButtonClick"
Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
public class NumberModel
{
public string ItemDisplayMember { get; set; }
public object ItemTag { get; set; }
public string ItemCategory { get; set; }
}
How can I group by ItemCategory property and bind it on GUI, a ComboBox for each ItemCategory and then multiple buttons in it?
Probably you don't need a ComboBox but Expander because the objective is reachable using it. ComboBox is needed when you have to filter something inside it or use dropdown displayed above Windows content.
I wrote a simple example using MVVM programming pattern. There will be many new classes but most of it you need add to the project only once. Let's go from the scratch!
1) Create class NotifyPropertyChanged to implement INotifyPropertyChanged interface. It needed to make able Binding to update the layout dynamically in Runtime.
NotifyPropertyChanged.cs
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
2) Create MainViewModel class derived from NotifyPropertyChanged. It will be used for Binding target Properties.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
public MainViewModel()
{
}
}
3) Attach MainViewModel to MainWindow's DataContext. One of ways - doing it in xaml.
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
4) Derive the data class NumberModel from NotifyPropertyChanged and add OnPropertyChanged call for each Property. This will give wonderful effect: when you change any Property in runtime, You'll immediately see the changes in UI. MVVM Magic called Binding :)
NumberModel.cs
public class NumberModel : NotifyPropertyChanged
{
private string _itemDisplayMember;
private object _itemTag;
private string _itemCategory;
public string ItemDisplayMember
{
get => _itemDisplayMember;
set
{
_itemDisplayMember = value;
OnPropertyChanged();
}
}
public object ItemTag
{
get => _itemTag;
set
{
_itemTag = value;
OnPropertyChanged();
}
}
public string ItemCategory
{
get => _itemCategory;
set
{
_itemCategory = value;
OnPropertyChanged();
}
}
}
5) When button is clicked I will not handle the Click event but call a Command. For easy use of Commands I suggest this relaying its logic class (grabbed here).
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly 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)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
6) All ready to fill the MainViewModel with code. I've added there a command and some items to collection for test.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<NumberModel> _itemsList;
private ICommand _myCommand;
public ObservableCollection<NumberModel> ItemsList
{
get => _itemsList;
set
{
_itemsList = value;
OnPropertyChanged();
}
}
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
if (parameter is NumberModel number)
MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
}));
public MainViewModel()
{
ItemsList = new ObservableCollection<NumberModel>
{
new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
};
}
}
7) The main answer to your main question is: use IValueConverter to filter the list with requred criteria. I wrote 2 converters. First for Categories, second for Buttons.
Converters.cs
public class CategoryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<NumberModel> collection)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (!result.Any(x => x.ItemCategory == item.ItemCategory))
result.Add(item);
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ItemGroupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (item.ItemCategory == categoryName)
result.Add(item);
}
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8) Now everything is ready to fill the markup. I post full markup here to make everything clear.
Note: I faced Visual Studio 2019 16.5.4 crash while setting a MultiBinding in ItemsSource and applied the workaround.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:CategoryConverter x:Key="CategoryConverter"/>
<local:ItemGroupConverter x:Key="ItemGroupConverter"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Expander Header="{Binding ItemCategory}">
<ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource">
<Setter.Value>
<MultiBinding Converter="{StaticResource ItemGroupConverter}">
<Binding Path="ItemsList"/>
<Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Button Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300"
Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Job is done with MVVM. Enjoy. :)
P.S. Ah, yes, I forgot to show you the code-behind class. Here it is!
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Student:
public class Student
{
private int _studentNo;
public int StudentNo
{
get { return _studentNo; }
set { _studentNo = value; }
}
private Rank _state;
public Rank State
{
get { return _state; }
set { _state = value; }
}
}
Rank:
public enum Rank
{
Pass,
Fail
}
RankBoard:
public class RankBoard:BindableBase
{
private static ObservableCollection<Student> _studentList;
public static ObservableCollection<Student> StudentList
{
get { return _studentList; }
set
{
_studentList = value;
}
}
static RankBoard()
{
LoadDetails();
}
private static void LoadDetails()
{
StudentList = new ObservableCollection<Student>()
{
new Student()
{
StudentNo=1,
State=Rank.Pass
},
new Student()
{
StudentNo=2,
State=Rank.Fail
},
new Student()
{
StudentNo=3,
State=Rank.Pass
},
};
}
public void ColorChanged(Student Items)
{
Student temp= _studentList.Where(i => i.StudentNo == Items.StudentNo).Single();
StudentList.Remove(temp);
StudentList.Add(Items);
}
}
BindableBase:
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
BackgroundChange:
public class BackgroundChange : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var b = value as Button;
if(b!=null)
{
foreach (var x in RankBoard.StudentList.Where(i=>i.StudentNo==System.Convert.ToInt32(b.Content)))
{
if ((x.State.ToString()=="Pass"))
{
return new SolidColorBrush(Colors.Green);
}
else
{
return new SolidColorBrush(Colors.Red);
}
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Mainwindow.xaml
<Window x:Class="ButtonColorChanged.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:ButtonColorChanged"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:BackgroundChange x:Key="Color"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0 30 0 0">
<TextBox x:Name="txt1" Width="75" Height="25"/>
<TextBox x:Name="txt2" Width="75" Height="25" Margin="20 0 20 0"/>
<Button x:Name="btnadd" Width="75" Height="25" Content="Add" Click="btnadd_Click"/>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<Button x:Name="btn1" Content="1" Width="75" Height="25" Background="{Binding ElementName=btn1,Converter={StaticResource Color}}"/>
<Button x:Name="btn2" Content="2" Width="75" Height="25" Margin="0 20 0 20" Background="{Binding ElementName=btn2,Converter={StaticResource Color}}"/>
<Button x:Name="btn3" Content="3" Width="75" Height="25" Background="{Binding ElementName=btn3,Converter={StaticResource Color}}"/>
</StackPanel>
</Grid>
</Window>
I want to change button color at runtime if i change the collection of the data.
but if i put dynamicresource, exception will throw. so how do i do?
Binding:
<Button x:Name="btn1" Content="1" Width="75" Height="25" Background="{Binding Path=ButtonColor}"/>
Your class:
public class RankBoard : BindableBase
{
private Color _buttonColor;
public Color ButtonColor
{
get
{
return _buttonColor;
}
set
{
_buttonColor = value;
RaisePropertyChanged("ButtonColor");
}
}
public void RefreshButtonColor(int content)
{
foreach (var x in StudentList.Where(i=>i.StudentNo==content))
{
if ((x.State.ToString()=="Pass"))
{
ButtonColor = Colors.Green;
}
else
{
ButtonColor = Colors.Red;
}
}
}
}
Call RefreshButtonColor whenever you want to change the color of the buttons according to the student list.
When I look at your code, you want to change the color for all button according to their content. If you use properties you have to define properties for each button. And in RefreshButtonColor you have to choose which property you want to update according to the input. This is just an example of how to change button color.
kind of missing here something: I'm using input Validation in a datatemplate (which works) and need to supply additional data to the validation (which doesn't work). Using "hard-coded" values (see comment in code below) works, using the binding doesn't.
The datatemplate is applied to elements (which have a "value", "low_lim" and "high_lim" properties) displayed in an listView (this also works). I think I just can't wrap my head around binding.
May be I just looked to long at this problem. If I'm right this is some kind of "visual-tree" problem with the DP...
Any help appreciated.
<DataTemplate DataType="{x:Type OptionA}" >
<TextBox Width="100" Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<val:NumberOptionValidator>
<val:NumberOptionValidator.validRange>
<val:Range low_lim="{Binding low_lim}" high_lim="{Binding high_lim}"/>
<!--<val:Range low_lim="1" high_lim="2"/>-->
</val:NumberOptionValidator.validRange>
</val:NumberOptionValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
I'd suggest you implement IDataErrorInfo on OptionA and not use the validation rule.
According to https://blogs.msdn.microsoft.com/wpfsdk/2007/10/02/data-validation-in-3-5/ in the "When to use Validation Rules vs. IDataErrorInfo" section, for "UI or business-layer validation logic":
Use Validation Rules:
The validation logic is detached from the data source, and can be reused between controls.
Use IDataErrorInfo: The validation logic is closer to the source.
Here is some code to get you started.
XAML:
<Window x:Class="WpfApplication33.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:WpfApplication33"
xmlns:val="clr-namespace:WpfApplication33"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:VM />
</Window.DataContext>
<Window.Resources>
<ControlTemplate x:Key="validationErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
<DataTemplate DataType="{x:Type local:OptionA}">
<TextBox Width="100"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}"
Text="{Binding value, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Items}" />
</Grid>
</Window>
CS:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication33
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VM
{
public List<OptionA> Items { get; set; }
public VM()
{
Items = new List<OptionA>() {
new OptionA() {value=1, low_lim = 0, high_lim = 2 },
new OptionA() {value=2, low_lim = 3, high_lim = 4 }, // too low
new OptionA() {value=3, low_lim = 2, high_lim = 5 },
new OptionA() {value=4, low_lim = 6, high_lim = 9 }, // too low
new OptionA() {value=5, low_lim = 0, high_lim = 4 }, // too high
};
}
}
public class OptionA : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private void OPC(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); }
private int _value, _low_lim, _high_lim;
public int value { get { return _value; } set { if (_value != value) { _value = value; OPC("value"); } } }
public int low_lim { get { return _low_lim; } set { if (_low_lim != value) { _low_lim = value; OPC("low_lim"); } } }
public int high_lim { get { return _high_lim; } set { if (_high_lim != value) { _high_lim = value; OPC("high_lim"); } } }
#region IDataErrorInfo
public string Error
{
get
{
return null;
}
}
public string this[string columnName]
{
get
{
string err = null;
if (columnName == "value")
{
if (value < low_lim || value > high_lim)
err = string.Format("Value is out of the range of {0} to {1}.", low_lim, high_lim);
}
return err;
}
}
#endregion
}
}
Screenshot:
I can't seem to find simple explanation of how to set it up can some one please help out?
I've read almost every tutorial and every single one don't explain completely, my problem is that I've already written some code but I am not sure what to write in the MainWindow.xamls.cs and how to get the validation to work.
Class
public class Person : IDataErrorInfo
{
public string Fname { get; set; }
public string Lname { get; set; }
public string Error
{
get { return ""; }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Fname")
{
if (string.IsNullOrEmpty(Fname))
{
result = "First name is required.";
return result;
}
string st = #"!|#|#|\$|%|\?|\>|\<|\*";
if (Regex.IsMatch(Fname, st))
{
result = "Contains invalid characters.";
return result;
}
}
if (columnName == "Lname")
{
if (string.IsNullOrEmpty(Lname))
{
result = "Cannot be empty.";
return result;
}
}
return null;
}
}
}
Xaml
<Window x:Class="WpfApplication2.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:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="eTemplate">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Blue" FontSize="13" Text="{Binding ElementName=adorned,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" >
</TextBlock>
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder x:Name="adorned"/>
</Border>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox Height="23" Validation.ErrorTemplate="{StaticResource ResourceKey=eTemplate}" HorizontalAlignment="Left" Margin="198,71,0,0" Name="Fname" VerticalAlignment="Top" Width="120" FontSize="15">
<TextBox.Text>
<Binding Path="Fname" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<TextBox Height="23" Validation.ErrorTemplate="{StaticResource ResourceKey=eTemplate}" HorizontalAlignment="Left" Margin="198,130,0,0" Name="Lname" VerticalAlignment="Top" Width="120" FontSize="15">
<TextBox.Text>
<Binding Path="Lname" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label Content="FirstName" FontSize="14" Height="28" HorizontalAlignment="Left" Margin="114,71,0,0" Name="FirstName" VerticalAlignment="Top" FontFamily="Consolas" RenderTransformOrigin="0.063,0.607" Width="84"/>
<Label Content="LastName" FontSize="14" Height="28" HorizontalAlignment="Left" Margin="114,130,0,0" Name="LastName" VerticalAlignment="Top" FontFamily="Consolas" Width="79"/>
<Button x:Name="Add" Content="test" HorizontalAlignment="Left" Margin="198,186,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
What do I do next?
Actually you've not implemented INotifyPropertyChanged interface so you property change notification is not performed. I've done some changes in your Person class as below;
public class Person : IDataErrorInfo, INotifyPropertyChanged
{
private string _fname;
private string _lname;
public String Fname
{
get { return _fname; }
set { _fname = value; OnPropertyChanged("Fname"); }
}
public String Lname
{
get { return _lname; }
set { _lname = value; OnPropertyChanged("Lname"); }
}
public string Error
{
get { return ""; }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Fname")
{
if (string.IsNullOrEmpty(Fname))
{
result = "First name is required.";
return result;
}
string st = #"!|#|#|\$|%|\?|\>|\<|\*";
if (Regex.IsMatch(Fname, st))
{
result = "Contains invalid characters.";
return result;
}
}
if (columnName == "Lname")
{
if (string.IsNullOrEmpty(Lname))
{
result = "Cannot be empty.";
return result;
}
}
return null;
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String param)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(param));
}
}
#endregion
}
And in MainWindow.cs class, just set the DataContext as Person class;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Person();
}
}