In a wpf dialog window I have a checkbox that enables and disables a textbox. The textbox has ValidatesOnDataErrors set to True. Via IDataErrorInfo I check the value of this textbox only, if the checkbox is checked.
My problem is that if the user checks the checkbox there is no new validation on textbox performed and therefor I dont get this red frame indicating an error.
For demonstration here is a small sample:
<Window x:Class="Validation.ValidationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ValidationWindow" Height="300" Width="300">
<DockPanel LastChildFill="False">
<CheckBox DockPanel.Dock="Top" IsChecked="{Binding InputAllowed}">Input allowed</CheckBox>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue</Label>
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True}" IsEnabled="{Binding InputAllowed}" Width="50"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue2</Label>
<TextBox Text="{Binding InputValue2, ValidatesOnDataErrors=True}" Width="50"/>
</StackPanel>
<Button DockPanel.Dock="Bottom" Click="OnOk">Ok</Button>
</DockPanel>
</Window>
code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Validation
{
/// <summary>
/// Interaction logic for ValidationWindow.xaml
/// </summary>
public partial class ValidationWindow : Window, IDataErrorInfo
{
public bool InputAllowed
{
get { return (bool)GetValue(InputAllowedProperty); }
set { SetValue(InputAllowedProperty, value); }
}
public static readonly DependencyProperty InputAllowedProperty =
DependencyProperty.Register("InputAllowed", typeof(bool), typeof(ValidationWindow), new PropertyMetadata(false));
public int InputValue
{
get { return (int)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValue", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public int InputValue2
{
get { return (int)GetValue(InputValue2Property); }
set { SetValue(InputValue2Property, value); }
}
public static readonly DependencyProperty InputValue2Property =
DependencyProperty.Register("InputValue2", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public ValidationWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnOk(object sender, RoutedEventArgs e)
{
string msg = Error;
if(!string.IsNullOrEmpty(Error))
{
MessageBox.Show(Error);
return;
}
DialogResult = true;
}
#region IDataErrorInfo Members
public string Error
{
get { return ((IDataErrorInfo)this)[null]; }
}
public string this[string columnName]
{
get
{
string msg = string.Empty;
if(string.IsNullOrEmpty(columnName))
{
msg += ((IDataErrorInfo)this)["InputValue"];
msg += ((IDataErrorInfo)this)["InputValue2"];
}
else
{
switch(columnName)
{
case "InputValue":
if(InputAllowed)
{
if(InputValue <= 0)
{
msg += "InputValue must be greater that 0!";
}
}
break;
case "InputValue2":
if(InputValue2 <= 0)
{
msg += "InputValue2 must be greater that 0!";
}
break;
}
}
return msg;
}
}
#endregion
}
}
We've been using this to force validation after programatcially changing text, should work as well if you call it in reponse to your checkbox's events:
var binding = someTextBox.GetBindingExpression( TextBox.TextProperty );
if( binding == null )
return;
binding.UpdateSource();
No actual difference from stijn's solution but since we're all a little lazy:
public static class DependencyPropertyExtensions
{
public static bool UpdateSource(this FrameworkElement source, DependencyProperty property)
{
var binding = source.GetBindingExpression(property);
if (binding != null)
{
binding.UpdateSource();
return true;
}
return false;
}
}
Related
This is a small test app to try and figure this problem out from my main app. I'll paste the code first.
XAML:
<Window
x:Class="ThreadTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThreadTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="Black" BorderThickness="2" Grid.Row="0"/>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0">
<Button Click="{x:Bind ViewModel.AddPosition}" Content="Add To List" Margin="5"/>
<TextBlock Text="{x:Bind ViewModel.OutputString, Mode=OneWay}" Margin="5"/>
<ListView ItemsSource="{x:Bind ViewModel.PositionCollection, Mode=OneWay}" Margin="5">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="ID" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Place" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PositionModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path=ID, Mode=OneWay}"/>
<TextBlock Grid.Column="1" Text="{x:Bind Path=Place, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
ViewModel (MainViewModel.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.UI.Dispatching;
using Windows.UI.Core;
using Windows.ApplicationModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ThreadTest {
public class MainViewModel : BindableBase, INotifyPropertyChanged {
private String outputString = "Empty";
public MainViewModel() {
}
public String OutputString {
get { return outputString; }
set { SetProperty(ref outputString, value); }
}
private Random _random = new Random();
private int _id = 0;
private ObservableCollection<PositionModel> _positioncollection = new();
public ObservableCollection<PositionModel> PositionCollection {
get { return _positioncollection; }
set { SetProperty(ref _positioncollection, value); }
}
public async void AddPosition() {
Progress<PositionModel> progress = new();
progress.ProgressChanged += Progress_ProgressChanged;
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClass positionsClass = new(ref _position, progress);
await Task.Run(() => { positionsClass.Start(); });
}
private void Progress_ProgressChanged(object sender, PositionModel e) {
// This is so I can see that the thread is actually running.
OutputString = Convert.ToString(e.Place);
}
}
}
BindableBase.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class BindableBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
}
}
PositionModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionModel {
/*
//Impliment INotifyPropertyChanged up above if using this.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
private int _id = 0;
public int ID {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private int _place = 0;
public int Place {
get { return _place; }
set { SetProperty(ref _place, value); }
}
*/
public int ID { get; set; }
public int Place { get; set; }
}
}
PositionsClass.cs:
using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionsClass {
private IProgress<PositionModel> _progress;
private PositionModel _position;
public PositionsClass(ref PositionModel position, IProgress<PositionModel> progress) {
_progress = progress;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_position.Place++;
// Send position back.
_progress.Report(_position);
Thread.Sleep(100);
}
}
}
}
So basically you'll click the 'add to list' button which will then create the PositionsClass positionsClass, create and populate the PositionModel _position, create an ObservableCollection PositionCollection (bound to listview on xaml) then spin off the class into it's own thread. The class will get _position and increase its .Place, then progress.report the _position back to main thread.
Now I'm trying to figure out how to get the PositionCollection (of ObservableCollection) to update the lisview ui. I subscribed to progress.ProgressChanged and update the OutputString just to make sure the class is actually running and incrementing which does work.
I've tried various things I've found on the web, including different inherited ObversableCollection methods, none of which work or I missunderstood them.
I thought implementing an INotifyPropertyChange on PositionModel.cs itself would work (the commented out code), but doing so brings up a cross thread error. I imagine it's because positionsClass on a seperate thread is updating .Place which is causing the cross thread error?
Can anyone help explain how to get the ObservableCollection to update the ui when it's property changes in my example above? Thanks! In my main app, I'll be updating a lot of properties on a separate thread, rather than just the 2 in this example. Which is why I thought it'd be easier to just send the whole model back in a progress.report.
I think I've figured it out. First I enable the INotifyProperty on PositionModel.cs code above (the commented out part). Then I add:
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public CoreDispatcher Dispatcher { get; }
To the MainViewModel, and modify AddPosition to:
public async void AddPositionDispatcher() {
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClassDispatcher positionsClassDispatcher = new(_position, _dispatcherQueue);
await Task.Run(() => { positionsClassDispatcher.Start(); });
}
Where I send a DispatcherQueue to the new modified PositionsClassDispatcher.cs:
using Microsoft.UI.Dispatching;
using System.Threading;
namespace ThreadTest {
internal class PositionsClassDispatcher {
private PositionModel _position;
DispatcherQueue _queue;
public PositionsClassDispatcher(PositionModel position, DispatcherQueue dispatcherQueue) {
_queue = dispatcherQueue;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_queue.TryEnqueue(() => {
_position.Place++;
});
Thread.Sleep(100);
}
}
}
}
Which will take the DispatcherQueue and use TryEnqueue to update _position.Place. ObvservableCollection now properly updates UI when properties are updated. Also update the XAML to use the new AddPositionDispatcher().
Also, having to use DispatcherQueue rather than Dispatcher as WinUI3 seems to not have have Dispatcher anymore.
Window.Dispatcher Property
Window.Dispatcher may be altered or unavailable in future releases. Use Window.DispatcherQueue instead.
Which has caused quite a few problems trying to figure this problem out, as a lot of info out there is based on Dispatcher rather than DispatcherQueue.
Hope this helps anyone else that runs into the problem.
Hi after i bind a combobox observablecollection , i want to add from a textbox to combobx.
adn it give me this error :
System.NullReferenceException: 'Object reference not set to an instance of an object.'
WpfApp1.MainWindow.getModel(...) returned null.
Image of the ERROR
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class Parts : Changed
{
public string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
RaisePropertyChanged("Name");
}
}
}
}
}
viewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class AddViewModel : Changed
{
private ObservableCollection<Parts> _persons;
public string names;
public AddViewModel()
{
Persons = new ObservableCollection<Parts>()
{
new Parts{Name="Nirav"}
,new Parts{Name="Kapil"}
,new Parts{Name="Arvind"}
,new Parts{Name="Rajan"}
};
}
public ObservableCollection<Parts> Persons
{
get { return _persons; }
set {
if (_persons != value)
{
_persons = value;
RaisePropertyChanged("Persons");
}
}
}
private Parts _sperson;
public Parts SPerson
{
get { return _sperson; }
set {
if (_sperson != value)
{
_sperson = value;
RaisePropertyChanged("SPerson");
}
}
}
}
}
MainWindow:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public AddViewModel addviewmodel;
public MainWindow()
{
InitializeComponent();
DataContext = new AddViewModel();
}
public AddViewModel getModel()
{
return addviewmodel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
getModel().Persons.Add(new Parts { Name = cmbtxt.Text});
}
}
}
XAML:
<Grid>
<ComboBox ItemsSource="{Binding Persons}" SelectedItem="{Binding Path=SPersons,Mode=TwoWay}" HorizontalAlignment="Left" Margin="391,17,0,0" VerticalAlignment="Top" Width="314" Height="27">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Name="cmbtxt" HorizontalAlignment="Left" Height="23" Margin="24,21,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="172" />
<Button Content="Add" HorizontalAlignment="Left" Margin="24,88,0,0" VerticalAlignment="Top" Width="156" Height="49" Click="Button_Click"/>
</Grid>
You could make addviewmodel a private readonly field and initialize it immediately. Then you simply have to set the DataContext to the field in the constructor.
Also, getModel() isn't very C#/.NET friendly. Use a property if you need to expose the field:
public partial class MainWindow : Window
{
private readonly AddViewModel addviewmodel = new AddViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = addviewmodel;
}
public AddViewModel AddViewModel => addviewmodel;
private void Button_Click(object sender, RoutedEventArgs e)
{
addviewmodel.Persons.Add(new Parts { Name = cmbtxt.Text });
}
}
Using a property you can actually remove the field altogether:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = AddViewModel;
}
public AddViewModel AddViewModel { get; } = new AddViewModel();
private void Button_Click(object sender, RoutedEventArgs e)
{
AddViewModel.Persons.Add(new Parts { Name = cmbtxt.Text });
}
}
In MainWindow, you never set a value to addviewmodel, hence it is null. You can fix it by changing your constructor:
public MainWindow()
{
InitializeComponent();
addviewmodel = new AddViewModel()
DataContext = addviewmodel ;
}
Here is a posting about NullReferenceException in general: What is a NullReferenceException, and how do I fix it?
I'm stumped on this one. Why would a command parameter be continuously empty despite being bound to a property that has a value.
XAML
<UserControl x:Class="CatalogInterface.ctlMainButtonPanel"
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:CatalogInterface"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MainButtonPanel">
<UserControl.Resources>
<local:MainButtonPanelViewModel x:Key="ViewModel" />
<!--#region Button Style-->
<!--#region FocusVisual-->
<!--#endregion FocusVisual-->
<!--#region Button-->
<!--#endregion Button-->
<!--#endregion Button Style-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<!--/////////////////////////////////////////////////////////////////////////////////-->
<!--////////This is the button that won't pass the CommandParameter//////////////////-->
<!--/////////////////////////////////////////////////////////////////////////////////-->
<Button x:Name="cmdConvertToFBook"
CommandParameter="{Binding SelectedDocument}"
Command="{Binding ConvertToFacebookCommand}"
Content="CONVERT TO FACEBOOK"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Column="0" Grid.Row="2" />
</UserControl>
View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
class MainButtonPanelViewModel : ViewModelBase
{
private Messanger _messanger = Messanger.Set_Messanger();
/////////////////////////////////////////////////////////////////////////////////
////////This is the property I'm binding my commandparameter to//////////////////
/////////////////////////////////////////////////////////////////////////////////
private object _selectedDocument
public object SelectedDocument
{
get { return _selectedDocument; }
set { _selectedDocument = value; }
}
public ConvertToFacebookCommand ConvertToFacebookCommand { get; set; }
public MainButtonPanelViewModel()
{
ConvertToFacebookCommand = new ConvertToFacebookCommand();
_messanger.Register(OnSentMessage, "DirFilesListBox_SelectedDocumentChanged");
}
public void OnSentMessage(object source, MessageEventArgs e)
{
_selectedDocument = e.MessageObject;
}
}
public class ConvertToFacebookCommand : SingleFuncitonBaseCommand
{
public override void ButtonFunction(object parameter)
{
ConvertToFacebookFn.Convert(parameter);
}
public override void NotExacutable(object parameter)
{
ButtonNotExacutable.Report(parameter);
}
}
}
ViewModelBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
public abstract class SingleFuncitonBaseCommand : ICommand
{
private bool _canExecute { get; set; } = true;
public SingleFuncitonBaseCommand()
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public virtual void Execute(object parameter)
{
if (_canExecute && parameter != null)
{
try
{
_canExecute = false;
ButtonFunction(parameter);
}
finally
{
_canExecute = true;
}
return;
}
NotExacutable(parameter);
}
public abstract void ButtonFunction(object parameter);
public abstract void NotExacutable(object parameter);
}
}
My SelectedDocument property is being set by the OnSentMessage event that updates the property when the user clicks on a File in a list box. I believe the object is a FileInfo object. I can confirm the event is firing properly and the property is being set.
Also if i change my SelectedDocument property to a readonly property and set it to a FileInfo object it then does get passed to the parameter.
Is it possible for my OnSentMessage event and CommandParameter binding to be operating on two different objects?
How can I debug this further?
Thanks All.
I am working on WPF, MVVM C# simple app for learning.
I do have my front-end having Table kind of structure using element ""
See "VehicalForm.xaml" below.
Below is code of my View as well as View-Model part. (I have given just necessary files. Please let me know if you need any other files)
App.xaml.cs
using Seris.ViewModels;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace Seris
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public void OnStartup(object sender, StartupEventArgs e)
{
VehicalForm vehicalForm = new VehicalForm();
vehicalForm.DataContext = new VehicalMainViewModel();
vehicalForm.Show();
}
}
}
VehicalForm.xaml
<Window x:Class="Seris.VehicalForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<WrapPanel Orientation="Vertical" Margin="10 " >
<Label Content="Vehical No" HorizontalAlignment="Left"/>
<TextBox Name="VehicalNo_Text" Height="23" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left"/>
<Label Content="Model" HorizontalAlignment="Left"/>
<TextBox Name="Model_Text" Height="23" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left" />
<Label Content="Manufacturing Date" HorizontalAlignment="Left"/>
<DatePicker/>
<Label Content="IU No" HorizontalAlignment="Left"/>
<TextBox Height="23" Name="IUNO_Text" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left"/>
<Label Content="Personnel" HorizontalAlignment="Left"/>
<ComboBox Name="Personnel_Combo" HorizontalAlignment="Left" Width="116"/>
<Separator Height="20" RenderTransformOrigin="0.5,0.5" Width="16"/>
<Button Name="Save_Button" Command="{Binding SaveToList}" Content="Save" Width="66"/>
<ListView Height="294" Width="371" >
<ListView Height="294" Width="371" ItemsSource="{Binding listItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Vehical No" DisplayMemberBinding="{Binding VehicalNo}" />
<GridViewColumn Header="Model" DisplayMemberBinding="{Binding Model}" />
<GridViewColumn Header="ManufacturingDate" DisplayMemberBinding="{Binding ManufacturingDate}" />
<GridViewColumn Header="IUNo" DisplayMemberBinding="{Binding IUNo}" />
<GridViewColumn Header="Personnel" DisplayMemberBinding="{Binding Personnel}" />
</GridView>
</ListView.View>
</ListView>
</WrapPanel>
VehicalForm.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Seris
{
public partial class VehicalForm : Window
{
public VehicalForm()
{
InitializeComponent();
}
}
}
VehicalMainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Seris.Models;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Seris.Commands;
using Seris.ViewModels;
namespace Seris.ViewModels
{
public class VehicalMainViewModel : ObservableObject
{
ObservableCollection<VehicalModel> listItems = new ObservableCollection<VehicalModel>();
#region Getter-Setter
private string _VehicalNo;
public string VehicalNo
{
get { return _VehicalNo; }
set
{
if (value != _VehicalNo)
{
_VehicalNo = value.Trim();
if(OnPropertyChanged("VehicalNo"))
listItems.Add(new VehicalModel(VehicalNo, Model, ManufacturingDate, IUNo, PersonnelName));
}
}
}
private string _Model;
public string Model
{
get { return _Model; }
set
{
if (value != _Model)
{
_Model = value.Trim();
OnPropertyChanged("Model");
}
}
}
private DateTime _ManufacturingDate;
public DateTime ManufacturingDate
{
get { return _ManufacturingDate; }
set
{
if (value != _ManufacturingDate)
{
_ManufacturingDate = value;
OnPropertyChanged("ManufacturingDate");
}
}
}
private string _IUNo;
public string IUNo
{
get { return _IUNo; }
set
{
if (value != _IUNo)
{
_IUNo = value.Trim();
OnPropertyChanged("IUNo");
}
}
}
private string _PersonnelName;
public string PersonnelName
{
get { return _PersonnelName; }
set
{
if (value != _PersonnelName)
{
_PersonnelName = value.Trim();
OnPropertyChanged("PersonnelName");
}
}
}
#endregion
private ICommand _saveButton_Command;
public ICommand SaveButton_Command
{
get { return _saveButton_Command; }
set { _saveButton_Command = value; }
}
public void SaveToList(object o1)
{
listItems.Add(new VehicalModel(VehicalNo,Model,ManufacturingDate,IUNo,PersonnelName));
}
public void RemoveFromList()
{
}
public VehicalMainViewModel()
{
VehicalModel vm=new VehicalModel();
SaveButton_Command = new RelayCommand(new Action<object>(SaveToList));
}
}
}
ObservableObject.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
namespace Seris.Models
{
public abstract class ObservableObject: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = PropertyChanged;
if(handler!=null)
{
if (propertyName != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
return false;
}
public void VerifyPropertyName(string propertyName)
{
if(TypeDescriptor.GetProperties(this)[propertyName]==null)
{
string msg = "Invalid Property Name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
public bool ThrowOnInvalidPropertyName { get; set; }
}
}
VehicalModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Seris.Models
{
public class VehicalModel : ObservableObject
{
#region Getter-Setter
private string _VehicalNo;
public string VehicalNo
{
get { return _VehicalNo; }
set
{
if (value != _VehicalNo)
{
_VehicalNo = value.Trim();
OnPropertyChanged("VehicalNo");
}
}
}
private string _Model;
public string Model
{
get { return _Model; }
set
{
if (value != _Model)
{
_Model = value.Trim();
OnPropertyChanged("Model");
}
}
}
private DateTime _ManufacturingDate;
public DateTime ManufacturingDate
{
get { return _ManufacturingDate; }
set
{
if (value != _ManufacturingDate)
{
_ManufacturingDate = value;
OnPropertyChanged("ManufacturingDate");
}
}
}
private string _IUNo;
public string IUNo
{
get { return _IUNo; }
set
{
if (value != _IUNo)
{
_IUNo = value.Trim();
OnPropertyChanged("IUNo");
}
}
}
private string _PersonnelName;
public string PersonnelName
{
get { return _PersonnelName; }
set
{
if (value != _PersonnelName)
{
_PersonnelName = value.Trim();
OnPropertyChanged("PersonnelName");
}
}
}
#endregion
#region Constructor
public VehicalModel(string VehicalNo, string Model, DateTime ManufacturingDate, string IUNo, string PersonnelName)
{
this.VehicalNo = VehicalNo;
this.Model = Model;
this.ManufacturingDate = ManufacturingDate;
this.IUNo = IUNo;
this.PersonnelName = PersonnelName;
}
public VehicalModel()
{
this.VehicalNo = null;
this.Model = null;
this.ManufacturingDate = DateTime.Now;
this.IUNo = null;
this.PersonnelName = null;
}
#endregion
#region Methods
#region Validate Methods
public bool Validate_VehicalNo()
{
if (matchRE(VehicalNo,"[A-Zz-z][A-Zz-z0-9]{6}"))
return true;
else
return false;
}
public bool Validate_Model()
{
if(Model!=null)
return true;
else
return false;
}
public bool Validate_ManufacturingDate()
{
return true;
}
public bool Validate_IUNo()
{
if(matchRE(IUNo,"[0-9]{10}"))
return true;
else
return false;
}
public bool Validate_PersonnelName()
{
if(matchRE(PersonnelName,"[A-Za-z]+"))
return true;
else
return false;
}
public bool matchRE(string stringToMatch, string regularExpression)
{
Regex regex = new Regex(#regularExpression);
Match match = regex.Match(stringToMatch);
if(match.Success)
return(true);
else
return(false);
}
#endregion
#endregion
}
}
What I need is
1) When I update VehicalNo, new row should be added in the table.
2) If I need to update individual elements of every row in future which should reflect in table as soon as I update , is there inbuilt facility in ListView? Or I need to use List for individual elements (i.e. VehicalNo, Model, ... ) and put in one main List keeping eye using ObservableObject?
I don't know eventhough it is being added to list as well as I have implemented INotifyPropert using ObservableObject, why its not reflecting in front-end.
Please help.
Add
public ObservableCollection ListItems {get{return listItems;}}
to you VehicalMainViewModel and change binding to
ItemsSource="{Binding ListItems}"
P.S. your listItems is private field.
ok so heres an example of the first Text box and you can follow suit on the rest:
<Window x:Class="Seris.VehicalForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Seris.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:VehicalMainViewModel/>
</Window.DataContext>
<Label Content="Vehical No" HorizontalAlignment="Left"/>
<TextBox Name="VehicalNo_Text" Height="23" TextWrapping="Wrap" Text="{Binding VehicalNo}" HorizontalAlignment="Left"/>
I cannot find any references at the moment but I would suggest looking at DataContext and Data Binding in WPF
EDIT
<Application x:Class="Seris.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="VehicalForm.xaml">
<Application.Resources>
</Application.Resources>
</Application>
I need Attached Property that sets focus to UIElement from ViewModel.
I've created Attached property and in PropertyChangedCallback I set focus to UIElement.
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
but I want it to work like trigger in shotgun. I set true to Test in ViewModel ...
it invokes PropertyChangedCallback in MyAttachedProperties class,sets focus to UIElement
((o as UIElement).Focus();
and value of Test in ViewModel returns to false
((o as UIElement).SetValue(VoySetFocusProperty, false);)
Everything seems to work fine but SetValue doesn't change my value in ViewModel back.
Full code:
View:
<Window x:Class="WpfAttachedProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAttachedProperty"
Title="MainWindow" Height="127" Width="316">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBox local:MyAttachedProperties.VoySetFocus="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Text="Focus Me"/>
<Button Grid.Row="1" Content="Click to Focus" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="75" Command="{Binding MyCommand}" />
</Grid>
Code Behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfAttachedProperty
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
/// <summary>
/// Command Class
/// </summary>
public class DelegateCommand:ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add
{
}
remove
{
}
}
}
/// <summary>
/// ViewModelClass
/// </summary>
public class ViewModel:INotifyPropertyChanged
{
private bool _test = false;
public bool Test
{
get
{
return _test;
}
set
{
_test = value;
this.NotifyPropertyChanged("Test");
}
}
public ICommand MyCommand
{
get
{
return new DelegateCommand(SetTestToTrue);
}
}
private void SetTestToTrue()
{
this.Test = true;
}
#region INotifyPropertyChanged
public void NotifyPropertyChanged(String PropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyAttachedProperties
{
public static Object GetVoySetFocus(DependencyObject obj)
{
return (Object)obj.GetValue(VoySetFocusProperty);
}
public static void SetVoySetFocus(DependencyObject obj, Object value)
{
obj.SetValue(VoySetFocusProperty, value);
}
public static readonly DependencyProperty VoySetFocusProperty =
DependencyProperty.RegisterAttached("VoySetFocus", typeof(bool), typeof(MyAttachedProperties), new UIPropertyMetadata(false, new PropertyChangedCallback(VoySetFocusChanged), new CoerceValueCallback(CoerceVoySetFocus)));
private static object CoerceVoySetFocus(DependencyObject d, object baseValue)
{
return (bool)baseValue;
}
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
// Doesn't set Test to false in ViewModel
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
}
}
Greetings Marko.
Issue is with the line:
((o as UIElement).SetValue(VoySetFocusProperty, false);)
You should instead use SetCurrentValue to set the DP.
((o as UIElement).SetCurrentValue(VoySetFocusProperty, false);)
Explanation:
Setting the value directly of any DependencyProperty breaks the binding it with source property.
However, SetCurrentValue doesn't break the binding and push back the value back to the source property. Explanation from MSDN for SetCurrentValue:
This method is used by a component that programmatically sets the
value of one of its own properties without disabling an application's
declared use of the property. The SetCurrentValue method changes the
effective value of the property, but existing triggers, data bindings,
and styles will continue to work.
Moreover, I think setting it in PropertyChanged callback won't propagate that back to Viewmodel if do the operation synchronously since it reaches to callback from Viewmodel property setter only.
So, what we can do is do this operation asynchronously by enqueuing it on dispatcher using BeginInvoke so that it gets propagate to viewmodel Test property.
(o as UIElement).Focus();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
// Doesn't set Test to false in ViewModel
(o as UIElement).SetCurrentValue(VoySetFocusProperty, false);
});