I am having an issue with the data bindings between my View and ViewModel. The bindings only run at initialize and then they will not update. The code runs as expected and I have no binding errors per the output window, but the bindings on the UI will not update.
Program Setup:
WPF
C#
Prism V8.1.97
MVVM
.NET Framework 4.7.2
Things I have tried:
XAML set to bind directly to the property with INotifyPropertyChanged
XAML set to Find the RelativeSource of type UserControl
RelayCommand to update the UI, with and without Invoke to the main thread.
BackgroundWorker to update the UI, with and without Invoke to the main thread.
DelegateCommand to update the UI, with and without Invoke to the main thread.
i.Interaction.EventTriggers with Click to Invoke a UI update on the main thread.
Everyone of these will run the code, but will not update the UI. I have left some of the code that I have tried in the program for BackgroundWorker, Delegate void, and Dispatcher.BeginInvoke. I have written a few programs and have never had this issue. Did I setup something wrong when I created the program?
UPDATE: There seems to be an issue with Visual Studio 2019. This might be only on my version as the other programs I have written no longer work. This could would normally run as intended.
UserControl I am trying to do a simple binding with. I created a Textbox at the bottom with Mode=TwoWay to see if the TextBlock would update.
<UserControl x:Class="MRC.Module.ModStatus.Views.ModStatusManagerView"
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:MRC.Module.ModStatus.Views"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cconv="clr-namespace:MRC.Framework.Core.Data.Converters;assembly=MRC.Framework.Core"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" >
<UserControl.Resources>
<cconv:RemoveSpacesConverter x:Key="IntToStringConvert"/>
</UserControl.Resources>
<Grid>
<Grid.Background>
<SolidColorBrush Color="#222222"/>
</Grid.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="DodgerBlue" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border BorderThickness="1" BorderBrush="DodgerBlue" VerticalAlignment="Stretch" Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}">
<StackPanel VerticalAlignment="Stretch">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Height="20" Margin="0,6" Content="{Binding StartStop}" Width="100"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click" >
<i:InvokeCommandAction Command="{Binding ProgressCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Margin="5,0" HorizontalAlignment="Center" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Health, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToStringConvert}}" FontSize="16" Foreground="White"/>
<TextBlock Margin="5,0" Text="{Binding TestingText}" Foreground="White" FontSize="16" AutomationProperties.Name="TestText"/>
</StackPanel>
<!--
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Health, UpdateSourceTrigger=PropertyChanged}"
-->
<ProgressBar Maximum="100" Minimum="0"
VerticalAlignment="Bottom" Height="25" Margin="5" />
<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.TestingText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="16" Height="30" Margin="5" Width="100"/>
</StackPanel>
</Border>
</Grid>
</Grid>
</Border>
<TextBlock HorizontalAlignment="Center" Grid.Column="1" VerticalAlignment="Center" Text="Mod Status"
FontSize="30" Foreground="Black"/>
</Grid>
</Grid>
</UserControl>
Code Behind
public partial class ModStatusManagerView : UserControl
{
public ModStatusManagerView()
{
InitializeComponent();
DataContext = new ModStatusManagerViewModel();
}
}
ViewModel
public class ModStatusManagerViewModel : ViewModelBase
{
#region Variables
private readonly BackgroundWorker worker = new BackgroundWorker();
private delegate void UpdateUIDelgate(string health, string Status);
#endregion
#region Commands
public ICommand ProgressCommand { get; private set; }
private void Testing(string health, string Status)
{
try
{
}
catch(Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
private bool CanProgressExecute()
{
return true;
}
private void Progress()
{
try
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
Health = 0;
StartStop = "Stop";
TestingText = "Initialized";
for (int i = 0; i < 99; i++)
{
Health = i;
System.Windows.Application.Current.MainWindow.UpdateLayout();
System.Threading.Thread.Sleep(100);
}
TestingText = "Completed";
System.Windows.MessageBox.Show("Complete");
}, System.Windows.Threading.DispatcherPriority.Render);
/*if (!System.Windows.Application.Current.Dispatcher.CheckAccess())
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new UpdateUIDelgate(Testing), "Stop", "Initialized");
return;
}*/
// System.Windows.Application.Current.MainWindow.Dispatcher.BeginInvoke(
// new UpdateUIDelgate(Testing), "Stop", "Initialized");
}
catch(Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
public ICommand ProgressOffCommand { get; }
private void ProgressOff()
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
StartStop = "Start";
});
}
#endregion
#region Constructor
public ModStatusManagerViewModel()
{
this.ProgressCommand = new RelayCommand(Progress);
//this.ProgressOffCommand = new RelayCommand(ProgressOff);
}
#endregion
#region Properties
public bool IsEnabled
{
get { return _isEnabled; }
set { SetProperty(ref _isEnabled, value); }
}
private bool _isEnabled = true;
/// <summary>
/// Gets or Sets the Max Health
/// </summary>
public string StartStop
{
get { return _startStop; }
set { SetProperty(ref _startStop, value); }
}
private string _startStop = "Start";
/// <summary>
/// Gets or Sets the Max Health
/// </summary>
public bool IsOn
{
get { return _isOn; }
set { SetProperty(ref _isOn, value); }
}
private bool _isOn = false;
/// <summary>
/// Gets or Sets the Max Health
/// </summary>
public double MaxHealth
{
get { return _maxHealth; }
set { SetProperty(ref _maxHealth, value); }
}
private double _maxHealth = 100;
/// <summary>
/// Gets or Sets the Min Health
/// </summary>
public double MinHealth
{
get { return _minHealth; }
set { SetProperty(ref _minHealth, value); }
}
private double _minHealth = 0;
/// <summary>
/// Gets or Sets the Min Health
/// </summary>
public double Health
{
get { return _Health; }
set { SetProperty(ref _Health, value); }
}
private double _Health = 0;
public string TestingText
{
get { return _testingText; }
set { SetProperty(ref _testingText, value); }
}
private string _testingText = "Waiting";
#endregion
#region Events
#endregion
#region Methods
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
while (IsOn)
{
Random rnd = new Random();
Health = rnd.Next(0, 100);
System.Threading.Thread.Sleep(150);
}
System.Threading.Thread.Sleep(150);
}
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//update ui once worker complete his work
}
#endregion
}
In case someone would like to see the implementation of the INotifyPropertyChanged
public abstract class ViewModelBase : ModelBase
{
public ViewModelBase()
{
}
~ViewModelBase()
{
}
public bool HasAnyErrors { get; set; }
}
Here is the ModelBase that ViewModelBase Implements
public abstract class ModelBase : BindableBase, INotifyPropertyChanged
{
protected ModelBase()
{
}
~ModelBase() { }
public bool HasIssues { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public static event PropertyChangedEventHandler StaticPropertyChanged = delegate { };
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static void OnStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(
typeof(ViewModelBase),
new PropertyChangedEventArgs(propertyName));
}
}
Here is the RelayCommand
public class RelayCommand : ICommand
{
#region Private Members
private Action _action;
private Action<object> _actionOb;
Action<object> _execteMethod;
Func<object, bool> _canexecuteMethod;
#endregion
#region Public Events
/// <summary>
/// Basic Command
/// </summary>
public event EventHandler CanExecuteChanged = (sender, e) => { };
#endregion
#region Constructors
/// <summary>
/// Default Constructor
/// </summary>
/// <param name="action"></param>
public RelayCommand(Action action)
{
_action = action;
}
/*public RelayCommand(System.Collections.ObjectModel.ObservableCollection<Frames.Model.Category> category, Action<object> action)
{
_actionOb = action;
}*/
/// <summary>
/// Default Constructor that passes an object
/// </summary>
/// <param name="execteMethod"></param>
/// <param name="canexecuteMethod"></param>
public RelayCommand(Action<object> execteMethod, Func<object, bool> canexecuteMethod)
{
_execteMethod = execteMethod;
_canexecuteMethod = canexecuteMethod;
}
/// <summary>
/// Default Constructor that determines if an action can execute before executing
/// </summary>
/// <param name="action"></param>
/// <param name="CanExecute"></param>
public RelayCommand(Action action, bool CanExecute)
{
if (CanExecute)
return;
_action = action;
}
public RelayCommand(Action<object> action, bool CanExecuteOb)
{
_actionOb = action;
}
#endregion
#region Command Methods
/// <summary>
/// Returns True if bool Parameter is not busy
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
if (parameter != null)
{
try
{
if ((bool)parameter)
return false;
return true;
}
catch
{
return true;
}
}
else
{
return true;
}
}
public bool CanExecuteOb(object parameter)
{
return true;
}
/// <summary>
/// Executes an Action that is not busy
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
Task.Run(async () =>
{
_action();
});
}
/// <summary>
/// Executes an Action that is not busy with a <href="Param">
/// </summary>
/// <param name="param"></param>
public void ExecuteOb(object param)
{
Task.Run(async () =>
{
_actionOb(param);
});
}
#endregion
}
UPDATE: It seems the functionality of SetProperty has stopped working.
I changed my properties from
public string TestingText
{
get { return _testingText; }
set { SetProperty(ref _testingText, value);}
}
private string _testingText = "Testing";
To
public string TestingText
{
get { return _testingText; }
set
{
if (_testingText == value)
return;
_testingText = value;
OnPropertyChanged("TestingText");
}
}
private string _testingText = "Testing";
everything is now working as it should be.
Related
recently I have started a new project in c#- Fireworks planning software. I am quite experienced in WinForms, but I want to learn WPF. So I have passed a couple of tutorials, but still on very beginning.
The task is to make a datagrid, where user puts data for each Firing point, thus for instance
Cue, EffectTime, DelayBeforeEffect, FiringTime and Name (it is not complete). Main window preview
No the thing is the time (EffectTime etc.) has to be input by user via keyboard and I want to:
if the value is like sss.fff (simple number or decimal number)
the program converts it automatically to TimeSpan (h:mm:ss.fff),
which is actually resolution of My firing system. Example: number
65.528 is converted to 0:01:05.528.
negative values are also allowed, because there are several
things have to be done before main pyrotechnics show starts
I would like validate user input it is valid time value, not for
instance 65.528.20. If the value is not correct I need either cancel
user leave the editedcell or at least rewrite the new value with
last validated
there is some relationship between values, for instance: FiringPoint
= EffectTime - Delay, or better FiringPoint = EffectTime.Subtract(Delay);
As I am used to I have created a UserControl TimeText with only one element - TextBox and I check, eventually correct, input upon points listed above.
Then I have created DataGrid and DataGridTemplateColumn in MainWindow,
classes like
FiringPoint (with properties listeda above),
FiringPointManager with ObservableCollection<FiringPoint> implementing INotifyChange interface
Data Binding between Manager and DataGrid.
After facing VisualStudio for about 20 hours, it is really working, but it seems to be very complicated.
Because for instance I need to forward DataGridRow.Background color to my TimeText control via dependencyProperty etc.
So finally my question: Is my approach correct or can somebody suggest better approach how to reach the same functionality?
Thanks for your opinions
Jan
Code for TimeTextBox
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
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 DataGridHratky
{
/// <summary>
/// Interakční logika pro TimeTextBox.xaml
/// </summary>
public partial class TimeTextBox : UserControl, INotifyPropertyChanged
{
#region Overrides
#endregion
#region Enum
#endregion
#region constants
private const string __NEG = "-";
private static readonly string[] __SEP = { ".", "," };
public const string __TFORMAT = #"%h\:mm\:ss\.fff";
private const string __COL = ":";
#endregion
#region private declarations
private bool _isNegative = false;
private double _onlySecondsValue = 0;
private Brush _okBackground;
private Brush _nokBackground = Brushes.OrangeRed;
private Brush _negBackground = Brushes.LavenderBlush;
private TimeSpan _tsValue = new TimeSpan();
private bool _bOnlySeconds = false;
private string _originalValue;
#endregion
#region private methods
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Determines if value is only in second format. May be also negative and decimal either with "." or ","
/// </summary>
/// <param name="text">text to check</param>
/// <param name="seconds">Out variable, where seconds should be stored</param>
/// <returns>true if so</returns>
private bool onlySeconds(string text, out double seconds)
{
if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator == __SEP[0])
text = text.Replace(__SEP[1], __SEP[0]);
else text = text.Replace(__SEP[0], __SEP[1]);
if (Double.TryParse(text, out seconds))
{
_bOnlySeconds = true;
return true;
} else
{
_bOnlySeconds = false;
return false;
}
}
private bool validate(string time)
{
string _rawValue = time;
if (onlySeconds(_rawValue, out _onlySecondsValue))
{
return true;
}
//check if it is negative
if (_rawValue.StartsWith(__NEG))
{
_isNegative = true;
}
else _isNegative = false;
if (TimeSpan.TryParse(_rawValue, out _tsValue)) return true;
else return false;
}
/// <summary>
/// Determines time based on:
/// - if there is only one : in string means minutes and seconds
/// - if there are two : means hours and minutes and seconds
/// - in both cases milliseconds after . or , may be present
/// </summary>
/// <param name="sTime">String representing validated time</param>
/// <returns>Time span with translated time</returns>
private TimeSpan determineTime(string sTime)
{
int _iColon = Regex.Matches(sTime,__COL).Count;
string _sResult;
TimeSpan _tsDays = new TimeSpan();
if (TimeSpan.TryParse(sTime, out _tsDays))
{
if (_tsDays.Days > 0)
return new TimeSpan(0, _tsDays.Hours, _tsDays.Minutes, _tsDays.Seconds, _tsDays.Milliseconds);
}
TimeSpan _ts = new TimeSpan();
if (_iColon == 1) //minutes and seconds
{
//add 0 hours and 0 days
_sResult = addTimeToTimeSpan(sTime, "0.0:");
}
else if
(_iColon == 2) //hours minutes and seconds
{
//add 0 days
_sResult = addTimeToTimeSpan(sTime, "0.");
}
else _sResult = sTime;
if (TimeSpan.TryParse(_sResult, out _ts))
{
// in all cases remove day
return new TimeSpan(0, _ts.Hours, _ts.Minutes, _ts.Seconds, _ts.Milliseconds);
}
else return _ts;
}
/// <summary>
/// Returns time with added value, moves __NEG sign if present to the from
/// </summary>
/// <param name="sTime">Original time</param>
/// <param name="sTimeToAdd">Time to add</param>
/// <returns>string with complete time</returns>
private string addTimeToTimeSpan(string sTime, string sTimeToAdd)
{
string _sResult;
if (sTime.StartsWith(__NEG))
{
_sResult = __NEG + sTimeToAdd + sTime.Remove(0, 1);
}
else _sResult = sTimeToAdd + sTime;
return _sResult;
}
#endregion
#region Public events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region delegates
#endregion
#region Public delcarations
#region Dependency
public Brush BKColor
{
get { return (Brush)GetValue(BKColorProperty); }
set { SetValue(BKColorProperty, value); }
}
// Using a DependencyProperty as the backing store for BKColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BKColorProperty =
DependencyProperty.Register("BKColor", typeof(Brush), typeof(TimeTextBox), new PropertyMetadata(null));
public bool ReadOnly
{
get { return (bool)GetValue(ReadOnlyProperty); }
set {
SetValue(ReadOnlyProperty, value);
timeTextBox.ReadOnly = value;
}
}
// Using a DependencyProperty as the backing store for ReadOnly. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ReadOnlyProperty =
DependencyProperty.Register("ReadOnly", typeof(bool), typeof(TimeTextBox), new PropertyMetadata(null));
public string TimeText
{
get { return (string)GetValue(TimeTextProperty); }
set {
SetValue(TimeTextProperty, value);
//OnPropertyChanged("TimeText");
}
}
public static readonly DependencyProperty TimeTextProperty =
DependencyProperty.Register("TimeText", typeof(string), typeof(TimeTextBox),
new PropertyMetadata(string.Empty, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
TimeTextBox myUserControl = dependencyObject as TimeTextBox;
myUserControl.OnPropertyChanged("TimeText");
myUserControl.OnTextPropertyChanged(e);
}
private void OnTextPropertyChanged(DependencyPropertyChangedEventArgs e)
{
txtTime.Text = TimeText;
}
public string Time
{
get { return txtTime.Text; }
set { txtTime.Text = value; }
}
#endregion
/// <summary>
/// True if time span is negative
/// </summary>
public bool IsNegative
{
get
{
return _isNegative;
}
}
/// <summary>
/// Gets or sets validated value background color
/// </summary>
public Brush ValueOKBackground
{
get
{
return _okBackground;
}
set
{
_okBackground = value;
}
}
/// <summary>
/// Gets or sets background color if value is not succesfully validated
/// </summary>
public Brush ValueNotOKBackground
{
get
{
return _okBackground;
}
set
{
_nokBackground = value;
}
}
#endregion
#region Public methods
#endregion
#region Constructors
public TimeTextBox()
{
InitializeComponent();
_okBackground = txtTime.Background;
}
#endregion
private void txtTime_TextChanged(object sender, TextChangedEventArgs e)
{
if (validate(txtTime.Text)) txtTime.Background = BKColor;
else
txtTime.Background = _nokBackground;
}
private void txtTime_LostFocus(object sender, RoutedEventArgs e)
{
//leave if nothing changed
if (txtTime.Text == _originalValue) return;
if (validate(txtTime.Text))
{
if (_bOnlySeconds)
{
int seconds = (int)Math.Truncate(_onlySecondsValue);
int milliseconds = (int)((_onlySecondsValue - seconds) * 1000);
_tsValue = new TimeSpan(0, 0, 0, seconds, milliseconds);
}
else
{
_tsValue = determineTime(txtTime.Text);
}
}
else
{
if (!validate(_originalValue)) //validate methods uses _tsValue to put validated timespan
{
_tsValue = new TimeSpan();
}
}
string _sign = _isNegative ? "-" : "";
txtTime.Text = _sign + _tsValue.ToString(__TFORMAT);
txtTime.Background = _isNegative ? _negBackground : BKColor;
TimeText = txtTime.Text;
txtTime.Background = BKColor;
OnPropertyChanged("UpdateTime");
}
private void txtTime_GotFocus(object sender, RoutedEventArgs e)
{
_originalValue = txtTime.Text;
if (!(validate(txtTime.Text))) txtTime.Text = "";
}
}
}
TimeText XAML
<UserControl x:Name="timeTextBox" x:Class="DataGridHratky.TimeTextBox"
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:DataGridHratky"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<!-- DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TimeTextBox}}-->
<Grid x:Name="grdLayout">
<TextBox x:Name="txtTime" TextChanged="txtTime_TextChanged" LostFocus="txtTime_LostFocus" GotFocus="txtTime_GotFocus" BorderThickness="0"
Text="{Binding Path=TimeText, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
IsReadOnly="{Binding Path=ReadOnly, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
Background="{Binding Path=BKColor, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
VerticalContentAlignment="Center"/>
</Grid>
</UserControl>
Main Window XAML
<Window x:Class="DataGridHratky.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:DataGridHratky"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<DataGrid x:Name="dgData" AutoGenerateColumns="False" AddingNewItem="dgData_AddingNewItem" BeginningEdit="dgData_BeginningEdit" CellEditEnding="dgData_CellEditEnding" RowHeight="25" AlternationCount="2" VerticalContentAlignment="Center" >
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="WhiteSmoke" />
</Trigger>
<DataTrigger Binding="{Binding Path=Selectable}" Value="False">
<DataTrigger.Setters>
<Setter Property="Background" Value="White" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="colCue" SelectedItemBinding="{Binding SelectedCue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="{Binding AvaiableCues}" Width="50" Header="Cue"/>
<DataGridTextColumn x:Name="colCaption" Width="200" Header="Popis" Binding="{Binding Caption}" />
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Čas efektu" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbFireEffect" TimeText="{Binding Effect, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridComboBoxColumn x:Name="colType" SelectedItemBinding="{Binding SelectedType, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="{Binding AvaiableFWTypes}" Header="Typ" Width="75" />
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Prodleni">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbDelay" TimeText="{Binding Delay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ReadOnly = "{Binding IsDelayReadOnly}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Čas odpalu" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbFirePoint" TimeText="{Binding FiringPoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
FirepointManager Code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataGridHratky
{
public class FirepointsManager : INotifyPropertyChanged
{
#region Enum
#endregion
#region constants
#endregion
#region private declarations
private List<string> _avaiableFirepoints = new List<string>();
private List<string> _avaiableFireworks = new List<string>();
#endregion
#region private methods
#endregion
#region Public events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected
protected void RaiseChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#endregion
#region delegates
#endregion
#region Public properties
/// <summary>
/// Firepoints Observable collection
/// </summary>
public ObservableCollection<FirePoint> FirePoints { get; set; }
/// <summary>
/// List of avaiable firepoints
/// </summary>
public List<string>AvaiableCues
{
get
{
return _avaiableFirepoints;
}
}
/// <summary>
/// List of pyrotechnics types
/// </summary>
public List<string>AvaiableFWTypes
{
get
{
return _avaiableFireworks;
}
}
#endregion
#region Public methods
/// <summary>
/// Adds a new Firepoint
/// </summary>
/// <param name="f">Firepoint</param>
public void AddFirePoint(FirePoint f)
{
f.SelectedCueChanged += F_SelectedCueChanged;
FirePoints.Add(f);
}
/// <summary>
/// Checks avaible cues and eliminates __MS if already used
/// </summary>
public void CheckSelectedCues()
{
foreach (var FirePoint in FirePoints)
{
if (FirePoint.SelectedCue == FirePoint.__MS)
{
if (_avaiableFirepoints.Contains(FirePoint.__MS))
_avaiableFirepoints.Remove(FirePoint.__MS);
return;
}
}
if (!_avaiableFirepoints.Contains(FirePoint.__MS))
_avaiableFirepoints.Add(FirePoint.__MS);
RaiseChange("CuesAvaiable");
}
private void F_SelectedCueChanged(object sender, EventArgs e)
{
CheckSelectedCues();
}
#endregion
#region Constructors
public FirepointsManager()
{
FirePoints = new ObservableCollection<FirePoint>();
FirePoints.CollectionChanged += FirePoints_CollectionChanged;
_avaiableFirepoints = FirePoint.GeneratedCues();
_avaiableFireworks = FirePoint.FireworksTypes();
}
private void FirePoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
CheckSelectedCues();
}
#endregion
}
}
I am writing a Directory explorer using the MVVM-Pattern.
My UI consists of a ComboBox which contains a path to a directory and a TreeView which acts as a Directory explorer.
The thing I am struggling with is to show the TreeView only when a ComboBox item is selected but I have no real idea how to achieve that.
Here is my code.
MainWindow.xaml
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="Select a Repository Directory" Margin="10,0" FontSize="16"/>
<ComboBox x:Name="cmbx" Margin="10,30,10,0" ItemsSource="{Binding CmbxItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
<TreeView x:Name="FolderView" Height="250" Margin="10,50,10,0" ItemsSource="{Binding Items}" Visibility="{Binding IsItemSelected}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Margin="5"
Source="{Binding Type,
Converter={x:Static local:HeaderToImageConverter.ConverterInstance}}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
ViewModel.cs
/// <summary>
/// ViewModel for the main Directory view.
/// </summary>
class ViewModel : BaseViewModel
{
#region Properties
public ObservableCollection<DirectoryStructureViewModel> Items { get; set; }
public ObservableCollection<string> CmbxItems { get; set; }
public bool _itemIsSelected = false;
public bool ItemIsSelected
{
get
{
return _itemIsSelected;
}
set
{
_itemIsSelected = value;
}
}
public string _selectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
ItemIsSelected = true;
MessageBox.Show("Selection changed");
_selectedItem = value;
}
}
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
public ViewModel()
{
CmbxItems = new ObservableCollection<string>(){};
CmbxItems.Add(DirectoryItem.rootPath);
//Get initial directory.
var children = DirectoryStructure.GetInitialDirectory();
//Create view models from data.
this.Items = new ObservableCollection<DirectoryStructureViewModel>(children.Select(dir => new DirectoryStructureViewModel(dir.FullPath, NodeTypes.Folder)));
}
#endregion
}
BaseViewModel.cs
/// <summary>
/// Base ViewModel that fires PropertyChanged events.
/// </summary>
public class BaseViewModel : INotifyPropertyChanged
{
//Event that is fired when aa child property changes its value.
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
DirectoryStructureViewModel.cs
public class DirectoryStructureViewModel : BaseViewModel
{
#region Properties
public NodeTypes Type { get; set; }
public string FullPath { get; set; }
public string Name { get { return DirectoryStructure.GetDirectoryOrFileName(this.FullPath); } }
/// <summary>
/// List of all children contained in this item.
/// </summary>
public ObservableCollection<DirectoryStructureViewModel> Children { get; set; }
/// <summary>
/// Indicates that this item can be expanded.
/// </summary>
public bool CanExpand { get { return this.Type != NodeTypes.File; } }
/// <summary>
/// Indicates if the current item is expanded.
/// </summary>
public bool IsExpanded
{
get
{
return this.Children?.Count(chldrn => chldrn != null) > 0;
}
set
{
if (value == true)
{
Expand();
}
else
{
this.ClearChildren();
}
}
}
public bool IsItemSelected { get; set; }
#endregion
#region Commands
public ICommand ExpandCommand { get; set; }
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="fullPath">Path of this item.</param>
/// <param name="type">Type of this item.</param>
public DirectoryStructureViewModel(string fullPath, NodeTypes type)
{
this.ExpandCommand = new TreeViewRelayCommand(Expand);
this.FullPath = fullPath;
this.Type = type;
this.ClearChildren();
}
#endregion
#region Helper Methods
//Removes all children from the list.
private void ClearChildren()
{
this.Children = new ObservableCollection<DirectoryStructureViewModel>();
if (this.Type != NodeTypes.File)
{
//Adds a dummy item to show the expand arrow.
this.Children.Add(null);
}
}
#endregion
#region Functions
/// <summary>
/// Expands this directory and finds all children.
/// </summary>
private void Expand()
{
if (this.Type != NodeTypes.File)
{
//Find all children
var children = DirectoryStructure.GetDirectoryContents(this.FullPath);
this.Children = new ObservableCollection<DirectoryStructureViewModel>(children.Select(content => new DirectoryStructureViewModel(content.FullPath, content.Type)));
}
else
{
return;
}
}
#endregion
}
Commands.cs
class TreeViewRelayCommand : ICommand
{
#region Members
private Action mAction;
#endregion
#region Events
/// <summary>
/// Event that is executed, when <see cref="CanExecute(object)"/> value has changed.
/// </summary>
public event EventHandler CanExecuteChanged = (sender, e) => { };
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="action"></param>
public TreeViewRelayCommand(Action action)
{
mAction = action;
}
#endregion
#region Command Methods
public bool CanExecute(object parameter)
{
return true;
}
/// <summary>
/// Executes the commands action.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
mAction();
}
#endregion
}
Edit: I am using FodyWeavers
You are almost there:
<TreeView ... Visibility="{Binding IsItemSelected}">
One problem is that you are binding Visibility to bool. There should be an binding error in Outputs window (check it now and always for various possible problems with WPF applications).
You can use BoolToVisibilityConverter (using this answer):
<someContainer.Resources>
<BooleanToVisibilityConverter x:Key="converter" />
</someContainer.Resources>
...
<TreeView ... Visibility="{Binding IsItemSelected, Converter={StaticResource converter}}">
Backing fields shouldn't be public.
You should (normally) rise notifications for all properties used in bindings. Typically at the end of setter.
I'd personally use getter only property:
string _selectedItem;
public string SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsItemSelected));
}
}
public bool IsItemSelected => SelectedItem != null;
Also you miss correct event rising method:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// can be public if you want to rise event from outside
protected void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
How to write WPF OpenFileDialog using MVVM (Model-View-ViewModel) in c#?
I have visited some websites regarding this OpenFileDialog ,but I didn't get clear idea about it. This is my code
In View.xaml:
<Window.... xmlns:VM="clr-namespace:myproject.myViewModel"
... >
<Window.DataContext><VM:myViewModel/>
</Window.DataContext>
<ItemsControl ItemsSource="{Binding mygroup}" >
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid >....
<TextBlock Text="Color" Margin="20" VerticalAlignment="Center"/>
<ComboBox KeyboardNavigation.TabIndex="0" Grid.Column="1" Margin="45,10,10,10" Height="30" Width="200" ItemsSource="{Binding Color}" />
<TextBlock Text="Shapes" Grid.Row="1" VerticalAlignment="Center" />
<ComboBox KeyboardNavigation.TabIndex="3" Grid.Column="1" Grid.Row="1" Height="20" Width="150" SelectedIndex="0" HorizontalContentAlignment="Right"
VerticalAlignment="Center" ItemsSource="{Binding Shapes}">
</ComboBox>
<TextBlock Text="Open Files " VerticalAlignment="Center" Grid.Row="0" Grid.Column="2" Margin="10" />
<TextBox Grid.Column="3" Text="" Height="30" Grid.Row="0" IsReadOnly="True"
TextAlignment="Right" VerticalContentAlignment="Center" Width="200" /> <Button Grid.Column="4" Content="Browse" Height="30" VerticalAlignment="Bottom" MinWidth="41" />
</Grid>
</Window>
In Model.cs:
namespace Myproject.Models
{
public class ProjectModel : INotifyPropertyChanged
{
private ObservableCollection<string> color;
private ObservableCollection<string> shapes;
public ObservableCollection<string> Color
{
get { return color; }
set
{
color = value;
NotifyPropertyChanged("Color");
}
}
public ObservableCollection<string> Shapes
{
get { return shapes; }
set
{
shapes = value;
NotifyPropertyChanged("Shapes");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
In ViewModel.cs:
namespace MyProject.ViewModels
{
public class myProjectViewModel : INotifyPropertyChanged
{
private ObservableCollection<myprojectmodel> mygroup;
public ObservableCollection<myprojectmodel> Mygroup
{
get => this.mygroup;
set
{
if (Equals(value, this.mygroup)) return;
this.mygroup = value;
OnPropertyChanged();
}
}
public ProjectViewModel()
{
Mygroup = new ObservableCollection<myprojectmodel>();
List<string> lstColor = new List<string>();
lstCity = new List<string> {"Pink", "Blue", "Red"};
List<string> lstShapes = new List<string>();
lstTemperature = new List<string> {"Rectangle", "Triangle", "Circle"};
Mygroup.Add(
new ProjectModel
{
Color= new ObservableCollection<string>(lstColor),
Shapes = new ObservableCollection<string>(lstShapes),
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
How should I write code to get OpenFileDialog.
I have seen this link WPF OpenFileDialog with the MVVM pattern? but I don't know how to write it for my above code.
please someone help me by editing my code to get OpenFileDailog.
In my opinion this kind of things doesn't belong in to the ViewModel. It's View specific logic. The View alone handles user input and then sends it to the ViewModel. ViewModel never asks the View to do something. This would invert the dependency chain and couple the ViewModel to the View. The dependencies have to be like this:
View --> ViewModel --> Model. The ViewModel doesn't even know about the type of views nor that there is a View at all.
You have to open the dialog from your view and then send the result to the view model.
For this you could create a simple event handler in your code-behind and attach it to a button's click event. You take the picked file and use an ICommand to invoke e.g. an open file action. That's MVVM (or MVP). Separate the concerns of the views from your models.
MainWindow.xaml:
<Window x:Class="WpfOpenDialogExample.OpenFileDialogSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="OpenFileDialogSample" Height="300" Width="300">
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<Grid>
<Button Name="ShowFilePickerButton" Click="ShowFilePicker_OnClick" Content="Open file" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.IO;
using System.Windows;
using Microsoft.Win32;
namespace WpfOpenDialogExample
{
public partial class OpenFileDialogSample : Window
{
public OpenFileDialogSample()
{
InitializeComponent();
}
private void ShowFilePicker_OnClick(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as ViewModel;
OpenFileDialog openFileDialog = new OpenFileDialog();
if(openFileDialog.ShowDialog() == true && viewModel.OpenFileCommand.CanExecute(openFileDialog.FileName))
{
viewModel.OpenFileCommand.Execute(openFileDialog.FileName);
}
}
private void ShowFolderPicker_OnClick(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as ViewModel;
FolderBrowserDialog openFolderDialog = new FolderBrowserDialog();
if(openFolderDialog.ShowDialog() == DialogResul.Ok && viewModel.OpenFolderCommand.CanExecute(openFolderDialog.SelectedPath ))
{
viewModel.OpenFolderCommand.Execute(openFolderDialog.SelectedPath );
}
}
}
}
ViewModel.cs:
public ICommand OpenFileCommand { get => new RelayCommand(OpenFile, CanOpenFile); }
private void OpenFile(string filePath)
{
...
}
private bool CanOpenFile(string filePath)
{
return File.Exists(filePath);
}
public ICommand OpenFolderCommand { get => new RelayCommand(OpenFolder, CanOpenFolder); }
private void OpenFolder(string folderPath)
{
...
}
private bool CanOpenFolder(string folderPath)
{
return Directory.Exists(filePath);
}
RelayCommand.cs:
using System;
using System.Windows.Input;
namespace WpfOpenDialogExample
{
/// <summary>
/// An implementation independent ICommand implementation.
/// Enables instant creation of an ICommand without implementing the ICommand interface for each command.
/// The individual Execute() an CanExecute() members are suplied via delegates.
/// <seealso cref="System.Windows.Input.ICommand"/>
/// </summary>
/// <remarks>The type of <c>RelaisCommand</c> actually is a <see cref="System.Windows.Input.ICommand"/></remarks>
public class RelayCommand : ICommand
{
/// <summary>
/// Default constructor to declare the concrete implementation of Execute(object):void and CanExecute(object) : bool
/// </summary>
/// <param name="executeDelegate">Delegate referencing the execution context method.
/// Delegate signature: delegate(object):void</param>
/// <param name="canExecuteDelegate">Delegate referencing the canExecute context method.
/// Delegate signature: delegate(object):bool</param>
public RelayCommand(Action<object> executeDelegate , Predicate<object> canExecuteDelegate)
{
this.executeDelegate = executeDelegate;
this.canExecuteDelegate = canExecuteDelegate;
}
/// <summary>
/// Invokes the custom <c>canExecuteDelegate</c> which should check wether the command can be executed.
/// </summary>
/// <param name="parameter">Optional parameter of type <see cref="System.Object"/></param>
/// <returns>Expected to return tue, when the preconditions meet the requirements and therefore the command stored in <c>executeDelegate</c> can execute.
/// Expected to return fals when command execution is not possible.</returns>
public bool CanExecute(object parameter)
{
if (this.canExecuteDelegate != null)
{
return this.canExecuteDelegate(parameter);
}
return false;
}
/// <summary>
/// Invokes the custom <c>executeDelegate</c>, which references the command to execute.
/// </summary>
/// <param name="parameter">Optional parameter of type <see cref="System.Object"/></param>
public void Execute(object parameter)
{
if (this.executeDelegate != null)
this.executeDelegate(parameter);
}
/// <summary>
/// The event is triggered every time the conditions regarding the command have changed. This occures when <c>InvalidateRequerySuggested()</c> gets explicitly or implicitly called.
/// Triggering this event usually results in an invokation of <c>CanExecute(object):bool</c> to check if the occured change has made command execution possible.
/// The <see cref="System.Windows.Input.CommandManager"/> holds a weakrefernce to the observer.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly Action<object> executeDelegate;
private readonly Predicate<object> canExecuteDelegate;
}
}
I've been stuck on this problem for a few hours. I am attempting to implement an MVVM-style Word Add-In in WPF. I am not using an MVVM toolkit. I have a WPF user control that is docked within a WinForm. While I am able to see the WPF user control within the win form and interact with it, my generic RelayCommand<T> that is bound to a WPF button won't execute when I click the button. The RelayCommand lives in ViewModel.cs and the DataContext for the view is set through the code-behind. I'm sure I'm doing something silly, but can't figure out what it is and therefore not sure why RelayCommand property's get{} won't get executed. Please see the code below. Thanks in advance for the help!
RelayCommand.cs (code snippet excludes namespace and includes statements)
/// <summary>
/// RelayCommand
/// </summary>
/// <typeparam name="T">Generic Parameter</typeparam>
public class RelayCommand<T> : ICommand where T : class
{
#region Constructors
/// <summary>
/// RelayCommand constructor
/// </summary>
/// <param name="exec">Delegate that encapsulates a method that takes in a single parameter and returns void</param>
/// <param name="canExec">Delegate that encapsulates a method that defines a set of criteria and returns a true if criteria is met; else false</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute is null");
_canExecute = canExecute;
_execute = execute;
}
#endregion
#region Members
/// <summary>
/// Execute method
/// </summary>
/// <param name="param">Parameter</param>
public void Execute(object param)
{
T obj = param as T;
if(obj != null)
{
_execute(obj);
}
}
/// <summary>
/// CanExec is a method that shows whether or not execution can happen
/// </summary>
/// <param name="param">Parameter</param>
/// <returns>true if can execute; else false</returns>
public bool CanExecute(object param)
{
if (_canExecute == null)
return true;
T obj = param as T;
return obj == null || _canExecute(obj);
}
/// <summary>
/// CanExec event changed
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
#region Fields
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
#endregion
}
SubmissionUserControl.xaml (only the pertinent snippet. excludes some code)
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
SubmissionUserControl.xaml.cs (contains snippet where I reference the ViewModel)
ViewModel viewModel;
public SubmissionUserControl()
{
InitializeComponent();
viewModel = new ViewModel();
DataContext = viewModel;
}
ViewModel.cs (excludes some code. only shows the pertinent RelayCommand)
/// <summary>
/// SubmitCommentCommand responsible for interacting with UI to submit a comment.
/// </summary>
/// <returns>Returns a RelayCommand that executes a method to Save comments from the comment box</returns>
public ICommand SubmitCommentCommand
{
get
{
return _submitCommentCommand ?? (_submitCommentCommand = new RelayCommand<object>(param => this.SaveComment()));
}
}
To give you a more detailed start into MVVM and RelayCommands:
You do not have to declare your ViewModel in Xaml, this is mostly done programmatically on application root level, maybe with some DI.
When sticking to this MSDN Article your RelayCommand should look like this:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
Additionally you can define a generic RelayCommand to handle Commandparameters like this:
public class GenericRelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
public Predicate<T> CanExecuteFunc { get; private set; }
public GenericRelayCommand(Action<T> execute) : this(execute, p => true)
{}
public GenericRelayCommand(Action<T> execute, Predicate<T> canExecuteFunc)
{
_execute = execute;
CanExecuteFunc = canExecuteFunc;
}
public bool CanExecute(object parameter)
{
var canExecute = CanExecuteFunc((T)parameter);
return canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
In your ViewModel the RelayCommands should be definied like this (I implemented INotifyPropertyChanged as well for further WPF Xaml Property handling example):
public class ViewModel : INotifyPropertyChanged
{
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
public GenericRelayCommand<string> SubmitComment1Command { get; set; }
public RelayCommand SubmitComment2Command { get; set; }
public ViewModel()
{
Comment = "Hello World!";
SubmitComment1Command = new GenericRelayCommand<string>(OnSubmitComment1);
SubmitComment2Command = new RelayCommand(OnSubmitComment2);
}
private void OnSubmitComment1(string obj)
{
//Save Comment Mock with CommandParameter
MessageBox.Show(obj);
}
private void OnSubmitComment2(object obj)
{
//Save Comment Mock with Property
MessageBox.Show(Comment);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I put your Button Example into a fresh WPF Application like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal">
<TextBox Name="textBox"
Width="200"
Text="{Binding Comment,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="SubmitButton1"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment1Command}"
CommandParameter="{Binding ElementName=textBox,
Path=Text}"
Content="Submit1" />
</StackPanel>
<Button x:Name="SubmitButton2"
Grid.Column="1"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment2Command}"
Content="Submit2" />
</Grid>
</Window>
And set the DataContext like this for simplicity reasons (As stated before, this is normally done through some kind of DI at root level):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Then everything should work fine.
I solved this issue by telling the Model about data context in XAML instead of .cs file.
First: Tell Model the namespace in which you placed your view model, mine was like below:
xmlns:Local="clr-namespace:MKPL.Views.A01.S020"
Second: Add your ViewModel in XAML Resources like:
<UserControl.Resources>
<Local:ViewModel x:Key="dvm"/>
</UserControl.Resources>
Third: Add DataContext to the parent container,in my case that is Grid.
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource dvm}}">
Fourth: In your button code add the data context like:
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand, Source={StaticResource dvm}}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
Hope it will help you
I have a WPF application (same app I have written about earlier) where I'm using the event handlers shown in Samuel Jack's page at http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html. I am trying to add an event handler to the TextBox in my UI to do the same action which happens when a button is pressed. However, it won't compile, the compilation error is this:
Error 2 A 'Binding' cannot be used within a 'TextBox' collection. A 'Binding' can
only be set on a DependencyProperty of a DependencyObject.
Here is the XAML (note, I want the same behaviour to happen where pressing Enter while the TextBox is in focus has the same action as clicking on the button:
Here is the View Model code:
/// <summary>
/// The ViewModel in the MVVM pattern
/// </summary>
public class ApplicationViewModel : INotifyPropertyChanged
{
#region Private members
private string searchString;
private string emailString;
private string toolName;
private NexusApp selectedApp;
private ICommand searchButtonCmd;
private ICommand clearButtonCmd;
private ICommand emailButtonCmd;
private ICommand textboxKeyCmd;
#endregion
#region INotifyPropertyChanged implementation
/// <summary>
/// Notifies the view that the observablecollection bound to the listview has changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Properties
/// <summary>
/// Collection bound to listview in ui view
/// </summary>
public ObservableCollection<NexusApp> AppCollection { get; set; }
/// <summary>
/// The application record selected by user
/// </summary>
public NexusApp SelectedApp
{
get
{
return this.selectedApp;
}
set
{
this.selectedApp = value;
this.emailString = this.selectedApp.Email;
this.toolName = this.selectedApp.Name;
}
}
/// <summary>
/// String entered by user into search box
/// </summary>
public string AppToSearch
{
get
{
return searchString;
}
set
{
searchString = value;
}
}
/// <summary>
/// Email address of team that owns app user is trying to request
/// </summary>
public string AppToRequest
{
get
{
return emailString;
}
set
{
emailString = value;
}
}
#endregion
/// <summary>
/// Default constructor, initialises and populates collection
/// </summary>
public ApplicationViewModel()
{
this.AppCollection = ApplicationsModel.Current;
}
#region Button command handlers
/// <summary>
/// Handles search button
/// </summary>
public ICommand SearchButtonPressed
{
get
{
if (this.searchButtonCmd == null)
{
this.searchButtonCmd = new RelayCommand(SearchButtonPressedExecute, c => CanSearch);
}
return this.searchButtonCmd;
}
}
/// <summary>
/// Handles clear button
/// </summary>
public ICommand ClearButtonPressed
{
get
{
if (this.clearButtonCmd == null)
{
this.clearButtonCmd = new RelayCommand(ClearButtonPressedExecute, c => CanClear);
}
return this.clearButtonCmd;
}
}
/// <summary>
/// Handles Request Application button
/// </summary>
public ICommand EmailButtonPressed
{
get
{
if (this.emailButtonCmd == null)
{
this.emailButtonCmd = new RelayCommand(EmailButtonPressedExecute, c => CanEmail);
}
return this.emailButtonCmd;
}
}
public ICommand TextBoxKeyPressed
{
get
{
if (this.textboxKeyCmd == null)
{
this.textboxKeyCmd = new RelayCommand(TextBoxKeyPressedExecute, c => CanSearch);
}
return this.textboxKeyCmd;
}
}
private void SearchButtonPressedExecute(object parameter)
{
try
{
ApplicationsModel.Current.Search(this.searchString);
}
catch (Exception e)
{
string error = String.Format("Could not refresh repository list: {0}", e.Message);
MessageBox.Show(error, "Error Performing Search", MessageBoxButton.OK, MessageBoxImage.Error);
}
NotifyPropertyChanged();
}
private void ClearButtonPressedExecute(object parameter)
{
try
{
this.searchString = String.Empty;
this.AppToSearch = String.Empty;
this.AppToSearch = String.Empty;
ApplicationsModel.Current.ClearSearch();
NotifyPropertyChanged();
}
catch (Exception e)
{
string error = String.Format("Could not refresh repository list: {0}", e.Message);
MessageBox.Show(error, "Error Clearing Search", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void EmailButtonPressedExecute(object parameter)
{
if (String.IsNullOrEmpty(this.toolName))
{
MessageBox.Show("Please select one of the applications to request", "Please select application", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
try
{
string message;
if (EmailHelper.IsValidEmail(this.emailString))
{
message = String.Format("Request for application {0} successfully sent", this.toolName);
}
else
{
message = String.Format("Could not send request for access\nThe email address is not defined for tool {0}.\nThe Equity Nexus team has been alerted, asking them to investigate", this.toolName);
}
ApplicationsModel.Current.Email(this.emailString, this.toolName);
MessageBox.Show(message, "Request Sent", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception e)
{
string error = String.Format("Could not send application request email, error: {0}", e.Message);
MessageBox.Show(error, "Erorr sending request", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void TextBoxKeyPressedExecute(object parameter)
{
MessageBox.Show("Hit textbox event execute");
}
/// <summary>
/// Ability to do search, always true
/// </summary>
public bool CanSearch
{ get { return true; } }
/// <summary>
/// Ability to clear search, always true
/// </summary>
public bool CanClear
{ get { return true; } }
/// <summary>
/// Ability to send email, always true
/// </summary>
public bool CanEmail
{ get { return true; } }
#endregion
}
Here is the RelayCommand code:
/// <summary>
/// Hooks up commands to the actions in Button Behaviour class
/// </summary>
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Here is the ButtonBehaviour code:
public static class ButtonBehaviour
{
public static readonly DependencyProperty SearchCommand;
public static readonly DependencyProperty ClearCommand;
public static readonly DependencyProperty EmailCommand;
public static readonly DependencyProperty TextBoxKeyCommand;
static ButtonBehaviour()
{
SearchCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "SearchCommand", typeof(ButtonBehaviour));
ClearCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "ClearCommand", typeof(ButtonBehaviour));
EmailCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "EmailCommand", typeof(ButtonBehaviour));
TextBoxKeyCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.KeyDownEvent, "EnterKeyCommand", typeof(ButtonBehaviour));
}
public static void SetSearchCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(SearchCommand, value);
}
public static ICommand GetSearchCommand(DependencyObject depobj)
{
return depobj.GetValue(SearchCommand) as ICommand;
}
public static void SetClearCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(ClearCommand, value);
}
public static ICommand GetClearCommand(DependencyObject depobj)
{
return depobj.GetValue(ClearCommand) as ICommand;
}
public static void SetEmailCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(EmailCommand, value);
}
public static ICommand GetEmailCommand(DependencyObject depobj)
{
return depobj.GetValue(EmailCommand) as ICommand;
}
public static void SetTextBoxKeyCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(TextBoxKeyCommand, value);
}
public static ICommand GetTextBoxKeyCommand(DependencyObject depobj)
{
return depobj.GetValue(TextBoxKeyCommand) as ICommand;
}
}
XAML code:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Height="84" HorizontalAlignment="Left" Margin="0,5,5,5" Name="imgNexusLogo" Stretch="Fill" VerticalAlignment="Top" Width="600" Source="nexus1bannerlong.png" />
<Grid Grid.Row="1" HorizontalAlignment="Center" Margin="0,5,5,5" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Search for Application">
<Label.Foreground>
<SolidColorBrush Color="LightCyan" />
</Label.Foreground>
</Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="3" Width="500" Text="{Binding AppToSearch}" vm:ButtonBehaviour.TextBoxKeyCommand="{Binding TextBoxKeyPressed}" />
<Button Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" Width="100" Height="20" Margin="3" Background="LightCyan" Content="Search" ToolTip="Click to filter application list by search value" vm:ButtonBehaviour.SearchCommand="{Binding SearchButtonPressed}" />
<Button Grid.Row="0" Grid.Column="3" HorizontalAlignment="Right" Width="100" Height="20" Margin="3" Background="LightCyan" Content="Clear Search" ToolTip="Click to restore application list to full listing" vm:ButtonBehaviour.ClearCommand="{Binding ClearButtonPressed}"/>
</Grid>
<ListView Grid.Row="2" BorderBrush="Black" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=AppCollection}" SelectedItem="{Binding SelectedApp}">
<ListView.View>
<GridView>
<GridViewColumn Header="Application Name" Width="200" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Application Description" Width="631" DisplayMemberBinding="{Binding Description}"/>
<GridViewColumn Header="Application Owner" Width="150" DisplayMemberBinding="{Binding Owner}"/>
</GridView>
</ListView.View>
</ListView>
<Button Grid.Row="3" HorizontalAlignment="Center" Width="200" Height="30" Margin="3" Background="LightCyan" Content="Request Application" ToolTip="Click here to contact app owners" vm:ButtonBehaviour.EmailCommand="{Binding EmailButtonPressed}" />
</Grid>
The Event Behaviour Factory code can be found at http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html, I have not made any changed to it.
Anyone know what I'm doing wrong? It's probably simple, but since I'm new to WPF and MVVM, I need to get my head around it.
Thanks!
Follow up
I just did a code behind and handled the event like that. It's not worth all of the headaches to avoid any code behinds, and this project has deadlines that have a higher priority that MVVM ideological purity. For any readers who are interested, here is what I did:
C# code:
private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ApplicationViewModel viewModel = DataContext as ApplicationViewModel;
if (viewModel != null)
{
viewModel.AppToSearch = textboxSearch.Text;
viewModel.SearchButtonPressedExecute(textboxSearch.Text);
}
}
}
XAML:
<TextBox Name="textboxSearch" Grid.Row="0" Grid.Column="1" Margin="3" Width="500" Text="{Binding AppToSearch}" KeyDown="TextBox_KeyDown"/>
I typically use Triggers to bind text box to a command in viewmodel.
<ComboBox Margin="5" ItemsSource="{Binding Configs}" SelectedItem="{Binding SelectedConfig, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding RetrieveSourceLayersCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
You need to add reference to this namespace in your xaml: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and need to add appropriate assembly in you project reference. In my case I added this assembly: Choose Project > Add Reference. In the Reference Manager dialog box, on the Extensions tab, check the listing for System.Windows.Interactivity. You'll find it under Assemblies > Extensions.