Data binding main window's title to view model's property - c#

I have a main window with following code:
<Window x:Class="CAMXSimulator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:View="clr-namespace:CAMXSimulator.View"
xmlns:ViewModel="clr-namespace:CAMXSimulator.ViewModel"
Icon="Resources/Images/Tractor.png"
Title="{Binding WindowTitle}"
Height="400" Width="600">
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:LogParserViewModel}">
<View:LogView />
</DataTemplate>
</Window.Resources>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border CornerRadius="5" BorderBrush="SteelBlue" BorderThickness="2" Grid.Row="2" Margin="0,5,5,0" >
<View:LogView />
</Border>
</Grid>
</Window>
in the LogParserViewModel.cs class i have the following
EDIT:
class LogParserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// public event PropertyChangedEventHandler PropertyChanged1;
private IDbOperations _camxdb;
#region private_virables
private string _vManageLogFile;
private string _camxNodes;
private IEnumerable<Tuple<string, string>> _camxnodesAsTuple;
RelayCommand _clearFieldscommand;
RelayCommand _runsimulationcommand;
private string _currentProfileName;
#endregion
#region Getters\Setters
public string CurrentProfileName
{
get { return _currentProfileName; }
set
{
_currentProfileName = value;
OnPropertyChanged("CurrentProfileName");
OnPropertyChanged("WindowTitle");
}
}
public string VManageLogFile
{
get { return _vManageLogFile; }
set { _vManageLogFile = value;
if(null != PropertyChanged)
{
// PropertyChanged(this, new PropertyChangedEventArgs("VManageLogFile"));
OnPropertyChanged("VManageLogFile");
}
}
}
public string CamxNodes
{
get { return _camxNodes; }
set
{
_camxNodes = value;
if (null != PropertyChanged)
{
//PropertyChanged1(this, new PropertyChangedEventArgs("CamxNodes"));
OnPropertyChanged("CamxNodes");
}
}
}
#endregion
protected void OnPropertyChanged(string name)
{
// PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#region Constructors
public LogParserViewModel()
{
// PropertyChanged1 = new PropertyChangedEventHandler();
//PropertyChanged += UpdateCamxWindowEvent;
PropertyChanged += (s, e) => { if (e.PropertyName == "VManageLogFile") UpdateCamxWindowEvent(s, e); };
//creates a instance of database object
_camxdb = new DbOperations();
}
#endregion
#region Event_Hendlers
/// <summary>
/// This event is called when vManageLog window has changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpdateCamxWindowEvent(object sender, EventArgs e)
{
if (_vManageLogFile == null)
return;
//creates object of parser
var parser = new VManageLogParser(_vManageLogFile);
//returns a tuple of string string
_camxnodesAsTuple = parser.Parse();
//creates a string as we see it in the CAMX window of the simulator
CamxNodes = parser.CamxWindowText2(_camxnodesAsTuple);
MyLogger.Logger.Info("The Tabs been updated");
CurrentProfileName = "CAMX Simulator";
}
#endregion
#region Drag & DragOver
public void DragOver(DragEventArgs args)
{
// As an arbitrary design decision, we only want to deal with a single file.
if (IsSingleTextFile(args) != null) args.Effects = DragDropEffects.Copy;
else args.Effects = DragDropEffects.None;
// Mark the event as handled, so TextBox's native DragOver handler is not called.
args.Handled = true;
}
public void Drop(DragEventArgs args)
{
using (new WaitCursor())
{
// Mark the event as handled, so TextBox's native Drop handler is not called.
args.Handled = true;
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
StreamReader fileToLoad = new StreamReader(fileName);
VManageLogFile = fileToLoad.ReadToEnd();
// DisplaySFMFileContents.Text = fileToLoad.ReadToEnd();
fileToLoad.Close();
}
}
// If the data object in args is a single file, this method will return the filename.
// Otherwise, it returns null.
private string IsSingleTextFile(DragEventArgs args)
{
// Check for files in the hovering data object.
if (args.Data.GetDataPresent(DataFormats.FileDrop, true))
{
string[] fileNames = args.Data.GetData(DataFormats.FileDrop, true) as string[];
// Check fo a single file or folder.
if (fileNames.Length == 1)
{
// Check for a file (a directory will return false).
if (File.Exists(fileNames[0]))
{
//Check for the file extention , we look only for txt extentions
FileInfo info = new FileInfo(fileNames[0]);
if (info.Extension == ".txt")
{
MyLogger.Logger.Info("Name of file: " + fileNames[0]);
// At this point we know there is a single file text file.);
return fileNames[0];
}
}
}
}
MyLogger.Logger.Warn("Not a single file");
return null;
}
#endregion
#region ClearCommand
public ICommand ClearFieldsCommand
{
get
{
if (_clearFieldscommand == null)
_clearFieldscommand = new RelayCommand(
() => ClearFields(),
() => CanClearWindows);
return _clearFieldscommand;
}
}
void ClearFields()
{
VManageLogFile = null;
CamxNodes = null;
}
bool CanClearWindows
{
get { return (VManageLogFile != null ); }
}
#endregion
#region RunSimulation
public ICommand RunSimulationCommand
{
get
{
if (_runsimulationcommand == null)
_runsimulationcommand = new RelayCommand(
() => RunSimulation(),
() => CanRunSimulation);
return _runsimulationcommand;
}
}
void RunSimulation()
{
using (new WaitCursor())
{
try
{ //inserting the CAMX nodes to the table
foreach (var camxNode in _camxnodesAsTuple)
{
_camxdb.Insert(camxNode);
}
}
catch (Exception ex )
{
MyLogger.Logger.FatalException("Cannot Insert to Database" , ex);
}
}
}
bool CanRunSimulation
{
get { return !GlobalMethods.IsEmpty(_camxnodesAsTuple); }
}
#endregion
}
}
And I am trying to change the windows title by seeting it but nothing is happens any idea why ?

As I can't see from the current code what the DataContext of the main.xaml is, I'm going to take a guess that it is itself (not set to something else). I'm going to further go and say that your intent is to set the main.xaml's DataContext to a ViewModel:
XAML:
<Window x:Class="Namespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding WindowTitle}">
<!-- YOUR XAML -->
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Where MainWindowViewModel.cs contains the property for the WindowTitle.
If you want a some other class to control the WindowTitle then you'll still need to have a ViewModel for your MainWindow (i.e. MainWindowViewModel.cs) that accepts messages somehow (events for tightly coupled, event aggregation for loosely coupled) to update that property.

Your property in ViewModel should be named WindowTitle instead of CurrentProfileName

Related

wpf UI not updating?

Going through:
WPF binding not updating the view
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netcore-3.1
and
WPF DataContext updated but UI not updated
I still can't see why the UI is not updating in the following case (my best guess is that the DataContext of the Grid to be updated is not updated) and am loosing my mind:
AppliedJobsModel.cs (has IPropertyChange implemented as some of the answers suggest):
public class AppliedJobsModel { }
public class AppliedJob : INotifyPropertyChanged
{
private string appliedDate;
private string url;
private string company;
private string description;
private string contact;
private string stack;
private string response;
private string interviewDate;
public AppliedJob(string[] entries)
{
appliedDate = entries[Consts.APPLIED_DATE_INDEX];
url = entries[Consts.URL_INDEX];
company = entries[Consts.COMPANY_INDEX];
description = entries[Consts.DESCRIPTION_INDEX];
contact = entries[Consts.CONTACT_INDEX];
stack = entries[Consts.STACK_INDEX];
response = entries[Consts.RESPONSE_INDEX];
interviewDate = entries[Consts.INTERVIEWDATE_INDEX];
}
public string AppliedDate
{
get {
return appliedDate;
}
set {
if (appliedDate != value)
{
appliedDate = value;
RaisePropertyChanged("AppliedDate");
}
}
}
public string Url
{
get
{
return url;
}
set
{
if (url != value)
{
url = value;
RaisePropertyChanged("Url");
}
}
}
public string Company
{
get
{
return company;
}
set
{
if (company != value)
{
company = value;
RaisePropertyChanged("Company");
}
}
}
public string Description
{
get
{
return description;
}
set
{
if (description != value)
{
description = value;
RaisePropertyChanged("Description");
}
}
}
public string Contact
{
get
{
return contact;
}
set
{
if (contact != value)
{
contact = value;
RaisePropertyChanged("Contact");
}
}
}
public string Stack
{
get
{
return stack;
}
set
{
if (stack != value)
{
stack = value;
RaisePropertyChanged("Stack");
}
}
}
public string Response
{
get
{
return response;
}
set
{
if (response != value)
{
response = value;
RaisePropertyChanged("Response");
}
}
}
public string InterviewDate
{
get
{
return interviewDate;
}
set
{
if (interviewDate != value)
{
interviewDate = value;
RaisePropertyChanged("InterviewDate");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
AppliedJobsViewModel.cs (has an observable collection that gets correctly updated when a button is clicked (in dbg)):
class AppliedJobsViewModel
{
private TexParser texParser;
public AppliedJobsViewModel() {
// TODO:
// -- do nothing here
}
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get;
set;
}
private ICommand _openTexClick;
public ICommand OpenTexClick
{
get
{
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
}
}
public bool CanExecute
{
get
{
// check if executing is allowed, i.e., validate, check if a process is running, etc.
return true;
}
}
public async Task ReadAndParseTexFile()
{
if (texParser == null)
{
texParser = new TexParser();
}
// Read file asynchronously here
await Task.Run(() => ReadFileAndUpdateUI());
}
private void ReadFileAndUpdateUI()
{
texParser.ReadTexFile();
string[][] appliedJobsArray = texParser.getCleanTable();
// Use this:
// https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
// Update collection here
List<AppliedJob> appliedJobsList = createAppliedJobsListFromTable(appliedJobsArray);
AppliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
}
private List<AppliedJob> createAppliedJobsListFromTable(string[][] table)
{
List<AppliedJob> jobsList = new List<AppliedJob>();
for (int i = 0; i < table.Length; i++)
{
jobsList.Add(new AppliedJob(table[i]));
}
return jobsList;
}
}
AppliedJobsView.xaml:
<UserControl x:Class="JobTracker.Views.AppliedJobsView"
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:JobTracker.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Name="appliedJobsGrid" Grid.Row="1" Grid.Column="1" Background="#50000000" Margin="10,10,10,10">
<ItemsControl ItemsSource = "{Binding Path = AppliedJobsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = AppliedDate, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Url, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Company, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Description, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Contact, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Stack, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = Response, Mode = TwoWay}" Width = "100" />
<TextBox Text = "{Binding Path = InterviewDate, Mode = TwoWay}" Width = "100" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
TrackerHome.xaml (main page/uses the user control):
<Grid Grid.Row="1" Grid.Column="1">
<views:AppliedJobsView x:Name = "AppliedJobsControl" Loaded = "AppliedJobsViewControl_Loaded" />
</Grid>
TrackerHome.cs:
public TrackerHome()
{
InitializeComponent();
// Set data context here (https://stackoverflow.com/questions/12422945/how-to-bind-wpf-button-to-a-command-in-viewmodelbase)
// https://stackoverflow.com/questions/33929513/populate-a-datagrid-using-viewmodel-via-a-database
if (appliedJobsViewModel == null)
{
appliedJobsViewModel = new AppliedJobsViewModel();
}
this.DataContext = appliedJobsViewModel;
//AppliedJobControl.DataContext = appliedJobsViewModel;
}
private void AppliedJobsViewControl_Loaded(object sender, RoutedEventArgs e)
{
if (appliedJobsViewModel == null)
{
appliedJobsViewModel = new AppliedJobsViewModel();
}
AppliedJobsControl.DataContext = appliedJobsViewModel;
}
You are setting a new value of property here:
AppliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
but it's a simple auto-property without notification.
Make it full property (view model needs to implement INotifyPropertyChange):
ObservableCollection<AppliedJob> _appliedJobsCollection =
new ObservableCollection<AppliedJob>(); // empty initially
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get => _appliedJobsCollection;
set
{
_appliedJobsCollection = value;
RaisePropertyChanged(nameof(AppliedJobsCollection));
}
}
How does the full property behave? Is it as if all entries in each item in the collection have been changed (and thus have their properties changed)?
See this pseudo-code.
// given that AppliedJobsCollection is already initialized
// modify existing collection -> works
// bindings was subscribed to CollectionChanged event and will update
AppliedJobsCollection.Add(new AppliedJob(...));
// change item property -> works
// you implement INotifyPropertyChanged for items
// bindings was subscribed to that and will update
AppliedJobsCollection[0].Company = "bla";
// new instance of collection -> ... doesn't works
// how bindings can update?
AppliedJobsCollection = new ObservableCollection<AppliedJob>(...);
For last scenario to work you need to implement INotifyPropertyChanged for a class containing AppliedJobsCollection property and rise notification.

c# wpf data binding not happening.

I am a bit new on c# WPF.
I have been following MVVM pattern and everything is set, my code seem to work fine but Issue I am facing is when I bind the data on xaml file, the data I am receiving from get set property but binding seems to have gone as no data is displayed on my text box. check my code.
/**********************xaml code***********************************\
<UserControl x:Class="ILS.debugger.debuggermsg"
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:serial="clr-namespace:ILS.VM.Serial_Monitor;assembly=ILS.VM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Text="{Binding Debugger_Recoreded}" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Background="#FFEBD3D3">
</TextBox>
</Grid>
</UserControl>
/***********************viewmodel code******************\
namespace ILS.VM.Serial_Monitor
{
public class serial : NotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
this.InvokePropertyChanged("Debugger_Recoreded");
}
}
/***********************model******************\
namespace ILS
public void OnDebugger(String Receved_Data) //debug message monitor code
{
try
{
this.serialData.Debugger_Recoreded += " " + DateTime.Now + " " + Receved_Data + Environment.NewLine;
this.serialData.Debugger_Recoreded += Environment.NewLine;
}
catch (Exception e)
{
}
}
public class serial : INotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
OnPropertyChanged("Debugger_Recoreded");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And set DataContext too, in main windows enter this lines:
this.DataContext = serialData;
Also you can use mode way to bind.
<TextBox Text="{Binding Debugger_Recoreded,Mode="Towway"}" />
In your code-behind (i.e your debuggermsg class), you have to instantiate and assign a DataContext:
public debuggermsg()
{
InitializeComponent();
this.DataContext = new serial();
}
It is required for DataBinding, so that you will be able to interact with your ViewModel's properties.
Then, modify your ViewModel like so:
public class serial : INotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
OnPropertyChanged("Debugger_Recoreded");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Implementation of OnPropertyChanged method is required to notify your view of a modification of your ViewModel's property.
Everything should be fine then.
When implementing INotifyPropertyChanged it's best to use [CallerMemberName] attribute, it's in the System.Runtime.CompilerServices, as you don't have to hardcode a string name of the calling property:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you can write your property like this:
private string debuggerRecorded;
public string DebuggerRecorded
{
get
{
return debuggerRecorded;
}
set
{
if (debuggerRecorded != value)
{
this.debuggerRecorded = value;
i--;
if (i == 0)
{
this.debuggerRecorded = String.Empty;
i = 1000;
}
OnPropertyChanged(); // No argument needed
}
}
}
By doing this you don't have to worry about spelling and you can freely change the names of your properties in the future, you don't have to remember to change it also in the OnPropertyChanged.
Assuming everything else works fine with your code you just need to set DataContext, which is usually done in MainWindow. For example, like this:
public partial class MainWindow : Window
{
private Serial viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new Serial();
this.DataContext = viewModel;
}
}
Also, you may want to write your text box with another property:
TextBox Text="{Binding DebuggerRecorded, UpdateSourceTrigger=PropertyChanged}" ...
If you omit this last part, the Text will get updated only when the TextBox loses focus.

Binding a textBlock text to a value in a custom class xaml c#

when i navigate to the new page where it should display the text, it appears empty
The Xaml code i have
xmlns:vm="using:Estimation"
<Page.DataContext>
<vm:PlayerClass/>
</Page.DataContext>
this is the textBlock im trying to bind the data too.
<TextBlock x:Name="PlayerOne"
Text="{Binding PlayerOneName}"
/>
The Class im binding is as follows
public class PlayerClass :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
private string name;
public string PlayerOneName { get { return this.name; }
set { this.name = value ;
NotifyPropertChanged(PlayerOneName); } }
}}
and the class im changing the content in the text box is
private void StartButton_Click(object sender, RoutedEventArgs e)
{
if (PlayerOneTextBox.Text == EnterNameText ||
PlayerTwoTextBox.Text == EnterNameText ||
PlayerThreeTextBox.Text == EnterNameText ||
PlayerFourTextBox.Text == EnterNameText)
{
MessageDialog msgBox = new MessageDialog("Please Enter All Names Before Continuing");
msgBox.ShowAsync();
}
else
{
// playerNames.PropertyChanged += new DependencyPropertyChangedEventHandler(playerNames_PropertyChanged);
this.DataContex.PlayerOneName = PlayerOneTextBox.Text;
MessageDialog msgBox = new MessageDialog(playerNames.PlayerOneName);
msgBox.ShowAsync();
playerNames.PlayerTwoName = PlayerTwoTextBox.Text;
playerNames.PlayerThreeName = PlayerTwoTextBox.Text;
playerNames.PlayerFourName = PlayerFourTextBox.Text;
Frame.Navigate(typeof(NewRoundPage));
}
}
In the constructor set the name
public PlayerClass ()
{
PlayerOneName = "Jabba De Hutt";
}
Also set a fallback value to provide an indicator of a failed binding situation:
Text="{Binding PlayerOneName, FallBack=Unknown}"
The navigate should not change the datacontext of the textbox, change the viewmodel instead
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var PlayerNames = e.Parameter as PlayerClass;
this.DataContext.PlayerOneName = PlayerNames.PlayerOneName;
}

MVVM ICommand.CanExecute parameter contains previous value

I've a hard time understanding why ICommand.CanExecutes always contains the previous value instead of the new value if a nested property is used instead of a normal property.
The problem is described below and I seriously can't figure out a way to fix this besides using some form of "Facade" pattern where I create properties in the viewmodel and hook them to their corresponding property in the model.
Or use the damn CommandManager.RequerySuggested event. The reason this is not optimal is because the view presents over 30 commands, just counting the menu, and if all CanExecute updates every time something changes, it will take a few seconds for all menuitems / buttons to update. Even using the example down below with only a single command and button together with the command manager it takes around 500ms for the button to enable/disable itself.
The only reason I can think of is that the CommandParameter binding is not updated before the CanExecute is fired and then I guess there is nothing you can do about it.
Thanks in advance :!
For example
Let's say we've this basic viewmodel
public class BasicViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
this.name = value;
RaisePropertyChanged("Name");
Command.RaiseCanExecuteChanged();
}
}
private Project project;
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
if (value != null) value.PropertyChanged += ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
}
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) {
Command.RaiseCanExecuteChanged();
}
public DelegateCommand<string> Command { get; set; }
public BasicViewModel()
{
this.Project = new Example.Project();
Command = new DelegateCommand<string>(this.Execute, this.CanExecute);
}
private bool CanExecute(string arg) {
return !string.IsNullOrWhiteSpace(arg);
}
private void Execute(string obj) { }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null) {
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and this model
public class Project : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaisePropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in my view I've this textbox and button.
<Button Content="Button" CommandParameter="{Binding Path=Project.Text}" Command="{Binding Path=Command}" />
<TextBox Text="{Binding Path=Project.Text, UpdateSourceTrigger=PropertyChanged}" />
It works, every time I type something in the textbox the CanExecute is invoked, BUT the parameter is always set to the previous value. Let say I write 'H' in the textbox, CanExecute is fired with parameter set to NULL. Next I write 'E', now the textbox contains "HE" and the CanExecute fires again. This time with the parameter set to 'H' only.
For some strange reason the parameter is always set to the previous value and when I check the Project.Text it's set to "HE" but parameter is still set to only 'H'.
If I now change the command parameter to
CommandParameter="{Binding Path=Name}"
and the Textbox.Text to
Text={Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"
everything works perfectly. The CanExecute parameter always contain the latest value and not the previous value.
The facade pattern you're talking about it standard WPF practice. The main problem with the way that you're doing it is that when events are raised, their subscribed event handlers execute in the order that they are subscribed. The line of code where you have:
if (value != null) value.PropertyChanged += ChildPropertyChanged;
This subscribes to the "PropertyChanged" Event of your "Project" class. Your UIElements are also subscribed to this same "PropertyChanged" event through your binding in the XAML. In short, your "PropertyChanged" event now has 2 subscribers.
The thing about events is that they fire in a sequence and what's happening in your code, is that when the event fires from your "Project.Text" it executes your "ChildPropertyChanged" event, firing your "CanExecuteChanged" event, which finally runs your "CanExecute" function(which is when you're seeing the incorrect parameter).
THEN, after that, your UIElements get their EventHandlers executed by that same event. And their values get updated.
It's the order of your subscriptions causing the problem. Try this and tell me if it fixes your problem:
public Project Project
{
get { return project; }
set {
if (project != null) project.PropertyChanged -= ChildPropertyChanged;
project = value;
RaisePropertyChanged("Project");
if (project != null) project.PropertyChanged += ChildPropertyChanged;
}
}
This is how I would have done this, and it works as expected. The only difference here is I'm using RelayCommand instead of DelegateCommand - they fundamentally have the same implementation so they should be interchangeable.
When the user enters the text and then clicks the button the execute method of the RelayCommand gets the expected text - simple.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Column="0"
Grid.Row="0"
Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="0"
Grid.Row="1"
Content="Test"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Command="{Binding Path=TextCommand, Mode=OneWay}" />
</Grid>
ViewModel:
public sealed class ExampleViewModel : BaseViewModel
{
private string _text;
public ExampleViewModel()
{
TextCommand = new RelayCommand(TextExecute, CanTextExecute);
}
public string Text
{
get
{
return _text;
}
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public ICommand TextCommand { get; private set; }
private void TextExecute()
{
// Do something with _text value...
}
private bool CanTextExecute()
{
return true;
}
}
I found this great attached property from swythan on the prism codeplex discussion forum that did the trick very well. Of course it does not answer why the command parameter is set to the previous value but it fixes the problem in a nice way.
The code is slightly modified from the source, enabling the possibility to use it on controls in a TabItem by calling HookCommandParameterChanged when the OnLoaded event is invoked.
public static class CommandParameterBehavior
{
public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
typeof(bool),
typeof(CommandParameterBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));
public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
{
return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
}
public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
{
target.SetValue(IsCommandRequeriedOnChangeProperty, value);
}
private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ICommandSource))
return;
if (!(d is FrameworkElement || d is FrameworkContentElement))
return;
if ((bool)e.NewValue)
HookCommandParameterChanged(d);
else
UnhookCommandParameterChanged(d);
UpdateCommandState(d);
}
private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
{
return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
}
private static void HookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);
// N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
// so we need to hook the Unloaded event and call RemoveValueChanged there.
HookUnloaded(source);
}
private static void UnhookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);
UnhookUnloaded(source);
}
private static void HookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded += OnUnloaded;
fe.Loaded -= OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded += OnUnloaded;
fce.Loaded -= OnLoaded;
}
}
private static void UnhookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded -= OnUnloaded;
fe.Loaded += OnLoaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded -= OnUnloaded;
fce.Loaded += OnLoaded;
}
}
static void OnLoaded(object sender, RoutedEventArgs e)
{
HookCommandParameterChanged(sender);
}
static void OnUnloaded(object sender, RoutedEventArgs e)
{
UnhookCommandParameterChanged(sender);
}
static void OnCommandParameterChanged(object sender, EventArgs ea)
{
UpdateCommandState(sender);
}
private static void UpdateCommandState(object target)
{
var commandSource = target as ICommandSource;
if (commandSource == null)
return;
var rc = commandSource.Command as RoutedCommand;
if (rc != null)
CommandManager.InvalidateRequerySuggested();
var dc = commandSource.Command as IDelegateCommand;
if (dc != null)
dc.RaiseCanExecuteChanged();
}
}
Source: https://compositewpf.codeplex.com/discussions/47338

How do I bind an Observable Collection of dynamic data to an array of UserControls

I've been teaching myself C# and WPF for a few months and I've been stuck on this latest problem for a few days. I receive a stream of data (bytes of numbers and flags) about numerous units every second. I want to display this data using a UserControl and update it with data bindings. Since the project will require 42 such units displayed in a window, I have placed the UserControls in an array. But, I can't get the data binding to work. I've stripped out as much code as I could but I know this is still a lot remaining. The problem is with the bindings. I just can't figure out the correct syntax.
Here is My UserControl
<UserControl x:Name="TestUserControl"
x:Class="Test.UserControl1">
<Label x:Name="userControlNameLabel" Content="UNIT x" />
<Label x:Name="powerLabel" Content=" Powered"/>
<Label Content="V in"/>
<Label x:Name="vinData" Content="{Binding Path=VinDisplay, ElementName=UserControl1}" >
</Grid>
</UserControl>
Here is my UserControl code behind:
namespace Test
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public UserControl1()
{
InitializeComponent();
}
private byte vin;
public byte Vin
{
get { return (byte)GetValue(VinProperty); }
set
{ SetValue(VinProperty, (byte)value);
NotifyPropertyChanged("Vin");
}
}
private int power;
public int Power
{
get { return power; }
set
{
power = value;
if (power == 1)
{
powerLabel.Background = LEDONCOLOR;
powerLabel.Foreground = BLACKFONT;
}
else
{
powerLabel.Background = LEDOFFCOLOR;
powerLabel.Foreground = WHITEFONT;
}
}
}
public string UserControlName
{
get { return userControlNameLabel.Content.ToString(); }
set { userControlNameLabel.Content = value; }
}
public static DependencyProperty VinProperty = DependencyProperty.Register("Vin", typeof(byte), typeof(UserControl));
public static DependencyProperty PowerProperty = DependencyProperty.Register("Power", typeof(int), typeof(UserControl));
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is the Observable Collection
namespace Test
{
class userData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public String name { get; private set; }
private byte voltageIn;
public byte VoltageIn
{
get
{
return voltageIn;
}
set
{
if (value != this.voltageIn)
{
this.voltageIn = value;
NotifyPropertyChanged();
}
}
}
private int power;
public int Power
{
get
{
return power;
}
set
{
if (value != this.power)
{
this.power = value;
NotifyPropertyChanged();
}
}
}
public userData(string name, byte voltageIn, int power)
{
this.name = name;
this.voltageIn = voltageIn;
this.power = power;
}
public userData(string name)
{
this.name = name;
this.voltageIn = 0x00;
this.power = 0;
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is my Main window
<Window x:Class="Test.MainWindow"
xmlns:src="clr-namespace:Test"
xmlns:local="clr-namespace:Test"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" >
<Grid>
<Grid>
<Label Content="HW 0"/>
<src:UserControl1 x:Name="hw0unit1" Vin="{Binding Path=_userData[0].VoltageIn, Mode=OneWay}" />
<src:UserControl1 x:Name="hw0unit2"/>
</Grid>
<Grid>
<Label Content="HW 1"/>
<src:UserControl1 x:Name="hw1unit1"/>
<src:UserControl1 x:Name="hw1unit2"/>
</Grid>
</Grid>
</Window>
And my main program
namespace Test
{
public partial class MainWindow : Window
{
DispatcherTimer hwScan;
UserControl1[] userCntrl = new UserControl1[4];
byte[] vinDisplay = new byte[4];
int[] powerDisplay = new int[4];
ObservableCollection<userData> _userData;
// Main Routine
public MainWindow()
{
InitializeComponent();
// Create dispatch timer to call hwTimer_Tick
...
// Save off UserControls for all units into Array for easier access
userCntrl[0] = hw0unit1;
userCntrl[1] = hw0unit2;
userCntrl[2] = hw1unit1;
userCntrl[3] = hw1unit2;
// Init userData arrays
_userData = new ObservableCollection<userData>();
for (int i = 0; i < Properties.Settings.Default.unit.Count; i++)
_userData.Add(new userData(Properties.Settings.Default.unit[i]));
}
private void hwTimer_Tick(object sender, EventArgs e)
{
// Hardcode data for testing
vinDisplay[i] = a hex constant
powerDisplay[i] = a hex constant
for (int i=0; i<4; i++)
{
_userData[i].VoltageIn = vinDisplay[i];
_userData[i].Power = powerDisplay[i];
}
}
}
}

Categories

Resources