WPF using SaveFileDialog in MVVM - wpf

I'm struggling with using SaveFileDialog in MVVM.
I'm using RelayCommand class and launch SaveAsFileCommand. Within the SaveAsFileCommand, using lambda expression I split two arguments to have:
Instance of RichTextBox control and targeted path (filePath)
Then I call DataIO.SaveAsFile(arguments[0], arguments[1]), using above arguments.
To create the SaveDialogBox in the View Layer I'm using 3 classes:
DialogBox, FileDialogBox and SaveFileDialogBox
In the XAML I create the SaveDialogBox and try to call SaveAsFileCommand using MultiBinding to pass those two Command Parameters.
To show the SaveDialogBox I use the button which is bindded to SaveDialogBox
The problem is: At this place the compiler complains that it can't execute multibinding for my SaveDialogBox because of being non-DependencyObject and non-DependencyProperty.
How can I solve that problem and correctly save the file using that DialogBox being in accordance with MVVM in my case???
XAML parts of code:
<Button Command="{Binding ElementName=SaveFileDB, Path=Show}" >
<Button.ToolTip>
<ToolTip Style="{StaticResource styleToolTip}" >
<TextBlock Text="Save" Style="{StaticResource styleTextBlockTP}" />
</ToolTip>
</Button.ToolTip>
<Image Source="Icon\Save.png"/>
</Button>
<local:SaveFileDialogBox x:Name="SaveFileDB" Caption="Save content to file..."
Filter="Text files (*.txt)|*.txt|All files(*.*)|*.*"
FilterIndex="0" DefaultExt="txt"
CommandFileOK="{Binding SaveAsFileCommand}" >
<local:SaveFileDialogBox.CommandParemeter>
<MultiBinding Converter="{StaticResource parametersConverter}">
<Binding ElementName="MainRichTextBox" />
<Binding ElementName="SaveFileDB" Path="Show" />
</MultiBinding>
</local:SaveFileDialogBox.CommandParemeter>
</local:SaveFileDialogBox>
SaveAsFileCommand:
private ICommand _SaveAsFileCommand;
public ICommand SaveAsFileCommand
{
get
{
if (_SaveAsFileCommand == null)
{
_SaveAsFileCommand = new RelayCommand(
argument =>
{
var arguments = (object[])argument;
DataIO.SaveAsFile(arguments[0], arguments[1]);
}
);
}
return _SaveAsFileCommand;
}
}
DataIO.SaveAsFile method:
public static void SaveAsFile(object argument0, object argument1)
{
System.Windows.Controls.RichTextBox richTB = argument0 as System.Windows.Controls.RichTextBox;
string path = (string)argument1;
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
TextRange textRange = new TextRange(richTB.Document.ContentStart, richTB.Document.ContentEnd);
textRange.Save(fileStream, DataFormats.Text);
}
}
RelayCommand class:
class RelayCommand : ICommand
{
private readonly Action<object> _Execute;
private readonly Func<object, bool> _CanExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_Execute = execute;
_CanExecute = canExecute;
}
public RelayCommand(Action<object> execute)
{
if (execute == null) throw new ArgumentNullException("execute");
_Execute = execute;
}
public bool CanExecute(object parameter)
{
return _CanExecute == null ? true : _CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_CanExecute != null) CommandManager.RequerySuggested += value;
}
remove
{
if (_CanExecute != null) CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_Execute(parameter);
}
}
DialogBox class:
public abstract class DialogBox : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
protected Action<object> execute = null;
public string Caption { get; set; }
protected ICommand show;
public virtual ICommand Show
{
get
{
if (show == null)
show = new RelayCommand(execute);
return show;
}
}
}
FileDialogBox class:
public abstract class FileDialogBox : CommandDialogBox
{
public bool? FileDialogResult { get; protected set; }
public string FilePath { get; set; }
public string Filter { get; set; }
public int FilterIndex { get; set; }
public string DefaultExt { get; set; }
protected Microsoft.Win32.FileDialog fileDialog = null;
protected FileDialogBox()
{
execute =
o =>
{
var values = (object[])o;
RelayCommand relCom1 = (RelayCommand)values[1];
fileDialog.Title = Caption;
fileDialog.Filter = Filter;
fileDialog.FilterIndex = FilterIndex;
fileDialog.DefaultExt = DefaultExt;
string filePath = "";
if (FilePath != null) filePath = FilePath; else FilePath = "";
//if (o != null) filePath = (string)o;
//if (o != null) filePath = (string)values[1];
if (o != null) filePath = relCom1.ToString();
if (!String.IsNullOrWhiteSpace(filePath))
{
fileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(filePath);
fileDialog.FileName = System.IO.Path.GetFileName(filePath);
}
FileDialogResult = fileDialog.ShowDialog();
OnPropertyChanged("FileDialogResult");
if (FileDialogResult.HasValue && FileDialogResult != null)
{
FilePath = fileDialog.FileName;
OnPropertyChanged("FilePath");
ExecuteCommand(CommandFileOK, FilePath);
};
};
}
public static DependencyProperty CommandFileOKProperty =
DependencyProperty.Register("CommandFileOK", typeof(ICommand), typeof(FileDialogBox));
public ICommand CommandFileOK
{
get { return (ICommand)GetValue(CommandFileOKProperty); }
set { SetValue(CommandFileOKProperty, value); }
}
}
SaveFileDialogBox class:
public class SaveFileDialogBox : FileDialogBox
{
public SaveFileDialogBox()
{
fileDialog = new SaveFileDialog();
}
}

The way I handle a requirement for user input in a dialog is to use a control which goes in the view but has no UI.
I split the command to be done into two pieces.
Essentially these are show the dialog and invoke a command when you finish.
The control shows a dialog which grabs data and then invokes a command you give it via binding.
Since this is control you can bind fine and it's in the visual tree so it can get a reference to the window.
Please see confirmationrequestor in this:
https://gallery.technet.microsoft.com/WPF-User-Notification-MVVM-98940828
That's intended for confirming the deletion of a record but the same principle can be extended to a file picker with a little modification.
From that, the command invoked once the user clicks and closes the dialogue can capture any variables you need. If you bind them:
private RelayCommand _confirmCommand;
public RelayCommand ConfirmCommand
{
get
{
return _confirmCommand
?? (_confirmCommand = new RelayCommand(
() =>
{
confirmer.Caption = "Please Confirm";
confirmer.Message = "Are you SURE you want to delete this record?";
confirmer.MsgBoxButton = MessageBoxButton.YesNo;
confirmer.MsgBoxImage = MessageBoxImage.Warning;
OkCommand = new RelayCommand(
() =>
{
// You would do some actual deletion or something here
UserNotificationMessage msg = new UserNotificationMessage { Message = "OK.\rDeleted it.\rYour data is consigned to oblivion.", SecondsToShow = 5 };
Messenger.Default.Send<UserNotificationMessage>(msg);
});
RaisePropertyChanged("OkCommand");
ShowConfirmation = true;
}));
}
}
From the confirmationrequestor, invoking that command:
public static readonly DependencyProperty ShowConfirmDialogProperty =
DependencyProperty.Register("ShowConfirmDialog",
typeof(bool?),
typeof(ConfirmationRequestor),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ConfirmDialogChanged)
)
{ BindsTwoWayByDefault = true }
);
private static void ConfirmDialogChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ConfirmationRequestor cr = (ConfirmationRequestor)d;
Window parent = Window.GetWindow(cr) as Window;
MessageBoxResult result = MessageBox.Show(parent, cr.Message, cr.Caption, cr.MsgBoxButton, cr.MsgBoxImage);
if (result == MessageBoxResult.OK || result == MessageBoxResult.Yes)
{
if (cr.Command != null)
{
cr.Command.Execute(cr.CommandParameter);
}
}
cr.SetValue(ShowConfirmDialogProperty, false);
}

Related

WPF ListBox SelectedItems determine Ctrl or Shift pressed for selection

I need to know, if the SelectedItems got filled when Ctrl or Shift was pressed or not. Is there an easy way (without creating a new controltemplate) to get this info? I prefer solutions without code behind.
Best regards
Yannik
You can wire up selection changed event, and check if Modifier Keys are pressed for Selection.
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var isCtrlorShiftDown = (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
if (isCtrlorShiftDown)
{
// Write your Logic Here;
}
}
I found a solution with minimal code behind.
The main concept is, that I attach to KeyDown and KeyUp events in the MainWindow and set a property "CurrentKeyboardKeyPressed" on the MainViewModel, which propagates the pressed key info to the child view models, which in turn fire a custom event with a special Selection class that has the currently pressed key info.
The posted source code is heavyli shortened and does not run at all. If somebody is interested in the working solution, just ask me and I will email it.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindow()
{
_mainWindowViewModel = new MainWindowViewModel();
InitializeComponent();
DataContext = _mainWindowViewModel;
}
private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Ctrl;
return;
}
if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Shift;
}
}
private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.None;
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
// child view models
private readonly ObservableCollection<TTSViewModel> _ttsViewModels;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public EventHandler<KeyboardKeyPressedEventArgs> CurrentKeyboardKeyPressedChanged;
public MainWindowViewModel()
{
_ttsViewModels = new ObservableCollection<TTSViewModel>();
}
public PressedKeyboardKey CurrentKeyboardKeyPressed
{
get { return _currentKeyboardKeyPressed; }
set
{
if (_currentKeyboardKeyPressed != value)
{
_currentKeyboardKeyPressed = value;
OnCurrentKeyboardKeyPressedChanged(_currentKeyboardKeyPressed);
}
}
}
// create child view models
public void PopulateTTSList(int itemsToCreated)
{
for (int i = 0; i < itemsToCreated; i++)
{
var tts = new TTSViewModel("TTS " + i);
tts.FractionSelectionChanged += OnTTSFractionSelectionChanged;
CurrentKeyboardKeyPressedChanged += tts.CurrentKeyboardKeyPressedChanged;
_ttsViewModels.Add(tts);
}
}
private void OnCurrentKeyboardKeyPressedChanged(PressedKeyboardKey currentKeyboardKeyPressed)
{
var handler = CurrentKeyboardKeyPressedChanged;
if (handler != null)
{
handler(this, new KeyboardKeyPressedEventArgs(currentKeyboardKeyPressed));
}
}
// selection changed handler for each child view model
private void OnTTSFractionSelectionChanged(object sender, ItemSelectionChangedEventArgs fractionSelectionChangedEventArgs)
{
var sendingTTS = sender as TTSViewModel;
if (fractionSelectionChangedEventArgs.Selection.PressedKeyOnSelection == PressedKeyboardKey.None)
{
// single selection action goes here
// ....
}
else
{
// multi selection action goes here
// ....
}
}
}
TTSViewModel.cs (child view model)
public class TTSViewModel : ViewModelBase
{
private readonly SmartObservableCollection<FractionViewModel> _currentlySelectedfractions;
public EventHandler<ItemSelectionChangedEventArgs> FractionSelectionChanged;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public TTSViewModel()
{
_currentlySelectedfractions = new SmartObservableCollection<FractionViewModel>();
_currentlySelectedfractions.CollectionChanged += CurrentlySelectedfractionsOnCollectionChanged;
}
public void CurrentKeyboardKeyPressedChanged(object sender, KeyboardKeyPressedEventArgs currentKeyboardKeyPressedEventArgs)
{
_currentKeyboardKeyPressed = currentKeyboardKeyPressedEventArgs.CurrentKeyboardKeyPressed;
}
private void CurrentlySelectedfractionsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (FractionSelectionChanged != null)
{
if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Replace && notifyCollectionChangedEventArgs.NewItems != null)
{
var removed = _oldSelectedfractions.Except(_currentlySelectedfractions);
var added = _currentlySelectedfractions.Except(_oldSelectedfractions);
var selection = new Selection<FractionViewModel>(added, removed, _currentKeyboardKeyPressed);
_oldSelectedfractions.Clear();
foreach (var currentlySelectedfraction in _currentlySelectedfractions)
{
_oldSelectedfractions.Add(currentlySelectedfraction);
}
var selectionChangedArgs = new ItemSelectionChangedEventArgs(selection);
FractionSelectionChanged(this, selectionChangedArgs);
}
}
}
}
Selection.cs
public sealed class Selection<T>
{
private readonly List<T> _added;
private readonly List<T> _removed;
private readonly PressedKeyboardKey _currentKeyboardKeyPressed;
public Selection()
{
_added = new List<T>();
_removed = new List<T>();
}
/// <summary>
/// Initializes a new instance of the <see cref="Selection{T}" /> class.
/// </summary>
/// <param name="addedItems">[NotNull]</param>
/// <param name="removedItems">[NotNull]</param>
/// <param name="currentKeyboardKeyPressed">The current keyboard key pressed.</param>
public Selection(IEnumerable<T> addedItems, IEnumerable<T> removedItems, PressedKeyboardKey currentKeyboardKeyPressed)
: this()
{
_added.AddRange(addedItems);
_removed.AddRange(removedItems);
_currentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public IReadOnlyList<T> Added
{
get { return _added; }
}
public IReadOnlyList<T> Removed
{
get { return _removed; }
}
public PressedKeyboardKey PressedKeyOnSelection
{
get { return _currentKeyboardKeyPressed; }
}
}
KeyboardKeyPressedEventArgs.cs
public sealed class KeyboardKeyPressedEventArgs : EventArgs
{
public KeyboardKeyPressedEventArgs(PressedKeyboardKey currentKeyboardKeyPressed)
{
CurrentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public PressedKeyboardKey CurrentKeyboardKeyPressed { get; private set; }
}
ItemSelectionChangedEventArgs.cs
public sealed class ItemSelectionChangedEventArgs : EventArgs
{
public ItemSelectionChangedEventArgs(Selection<FractionViewModel> newSelection)
{
Selection = newSelection;
}
public Selection<FractionViewModel> Selection { get; private set; }
}
PressedKeyboardKey.cs
public enum PressedKeyboardKey
{
None,
Ctrl,
Shift
}

Markup extensions with state in WPF

I've just discovered that WPF Markup extension instances are reused in control templates. So each copy of the control template gets the same set of markup extensions.
This doesn't work if you want the extension to maintain some state per control it is attached to. Any idea how to solve this.
Don't store state in the Markup extension. Store it another way. For example.
public abstract class DynamicMarkupExtension : MarkupExtension
{
public class State
{
public object TargetObject { get; set; }
public object TargetProperty { get; set; }
public void UpdateValue(object value)
{
if (TargetObject != null)
{
if (TargetProperty is DependencyProperty)
{
DependencyObject obj = TargetObject as DependencyObject;
DependencyProperty prop = TargetProperty as DependencyProperty;
Action updateAction = () => obj.SetValue(prop, value);
// Check whether the target object can be accessed from the
// current thread, and use Dispatcher.Invoke if it can't
if (obj.CheckAccess())
updateAction();
else
obj.Dispatcher.Invoke(updateAction);
}
else // TargetProperty is PropertyInfo
{
PropertyInfo prop = TargetProperty as PropertyInfo;
prop.SetValue(TargetObject, value, null);
}
}
}
}
public sealed override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
State state = new State();
if (target != null)
{
state.TargetObject = target.TargetObject;
state.TargetProperty = target.TargetProperty;
return ProvideValueInternal(serviceProvider, state);
}
else
{
return null;
}
}
protected abstract object ProvideValueInternal(IServiceProvider serviceProvider, State state);
}
is a base class for handling the type of problem where you need to update the property the markup
extension is attached to at run time. For example a markup extension for binding to ISubject as
<TextBox Text="{Markup:Subscription Path=Excenter, ErrorsPath=Errors}"/>
using the SubscriptionExtension as below. I had had trouble with the code when I used it
within templates but I fixed it so the MarkupExtension did not store state in itself
using ReactiveUI.Ext;
using ReactiveUI.Subjects;
using ReactiveUI.Utils;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : DynamicMarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
[ConstructorArgument("errorsPath")]
public PropertyPath ErrorsPath { get; set; }
public SubscriptionExtension() { }
Maybe<Exception> currentErrorState = Maybe.None<Exception>();
public SubscriptionExtension(PropertyPath path, PropertyPath errorsPath)
{
Path = path;
ErrorsPath = errorsPath;
}
class Proxy : ReactiveObject, IDataErrorInfo, IDisposable
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
public string Error
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public string this[string columnName]
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public IObservable<Maybe<Exception>> Errors { get; set; }
public Maybe<Exception> currentError = Maybe.None<Exception>();
private CompositeDisposable Subscriptions = new CompositeDisposable();
public Proxy(IObservable<Maybe<Exception>> errors)
{
Errors = errors;
var subscription = errors.Subscribe(e => currentError = e);
Subscriptions.Add(subscription);
}
public void Dispose()
{
Subscriptions.Dispose();
}
}
protected override object ProvideValueInternal(IServiceProvider serviceProvider, DynamicMarkupExtension.State state )
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
DependencyPropertyChangedEventHandler myd = delegate(object sender, DependencyPropertyChangedEventArgs e){
state.UpdateValue(MakeBinding(serviceProvider, frameworkElement));
};
frameworkElement.DataContextChanged += myd;
return MakeBinding(serviceProvider, frameworkElement);
}
private object MakeBinding(IServiceProvider serviceProvider, FrameworkElement frameworkElement)
{
var dataContext = frameworkElement.DataContext;
if (dataContext is String)
{
return dataContext;
}
ISubject<string> subject = Lens.Empty<string>().Subject;
IObservable<Maybe<Exception>> errors = Observable.Empty<Maybe<Exception>>();
Binding binding;
Proxy proxy = new Proxy(errors);
bool madeit = false;
if (dataContext != null)
{
subject = GetProperty<ISubject<string>>(dataContext, Path);
if (subject != null)
{
errors = GetProperty<IObservable<Maybe<Exception>>>
(dataContext
, ErrorsPath) ?? Observable.Empty<Maybe<Exception>>();
proxy = new Proxy(errors);
}
madeit = true;
}
if(!madeit)
{
subject = new BehaviorSubject<string>("Binding Error");
}
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.TwoWayBindTo(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e, v) => subscription.Dispose();
binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value"),
ValidatesOnDataErrors = true
};
return binding.ProvideValue(serviceProvider);
}
private static T GetProperty<T>(object context, PropertyPath propPath)
where T : class
{
if (propPath==null)
{
return null;
}
try
{
object propValue = propPath.Path
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue as T;
}
catch (NullReferenceException e)
{
throw new MemberAccessException(propPath.Path + " is not available on " + context.GetType(),e);
}
}
}
}

How to pass CommandParameters to the ViewModel?

I have a command that should switch the current view when it's executed. I binded this command to my buttons like this:
<Button Style="{StaticResource TextButton}" Command="{Binding ViewModel:MainViewModel.OpenItemCommand}" CommandParameter="{Binding Link}"/>
I want to pass Link (the link of the currently selected article) to my command. My command is defined like this:
public class Command : ICommand
{
public event EventHandler CanExecuteChanged;
readonly Predicate<Object> _canExecute;
readonly Action<Object> _executeAction;
public Command(Predicate<Object> canExecute, Action<object> executeAction)
{
_canExecute = canExecute;
_executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
return _canExecute(parameter);
return true;
}
public void UpdateCanExecuteState()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
public void Execute(object parameter)
{
if (_executeAction != null)
_executeAction(parameter);
UpdateCanExecuteState();
}
}
In my ViewModel I have this:
public ICommand OpenItemCommand
{
get
{
if (_openItemCommand == null)
{
_openItemCommand = new Command.Command(
p => true,
p => OpenItem(_HOW_DO_I_GET_THE_PARAMETER?_)
);
}
return _openItemCommand;
}
set
{
if (_openItemCommand != value)
{
_openItemCommand = value;
RaisePropertyChanged("OpenItemCommand");
}
}
}
private void OpenItem(Uri link)
{
throw new NotImplementedException();
}
When I create the command I need to pass the command parameter (the link) to the Execute method. But how do I get the value of this? I defined the CommandParameter in XAML, but I don't know how to access it.
I really searched through a huge amount of websites but I can't really find the answer.
You should look at the implementation of Prism's DelegateCommand or MVVM light's RelayCommand. With these you would write code like this:
public class ViewModel
{
public ViewModel()
{
OpenItemCommand = new RelayCommand<string>(OpenItem);
}
public RelayCommand<string> OpenItemCommand { get; private set; }
private void OpenItem(string link)
{
Debug.WriteLine(link);
}
}
where string in this case is the type of the parameter.
I'm not sure where the link parameter is coming from but if it's from a control, the value of the control could be bound to a property of your view model, then you don't need a parameter, for example:
public class ViewModel
{
public ViewModel()
{
OpenItemCommand = new RelayCommand(OpenItem);
}
public RelayCommand OpenItemCommand { get; private set; }
public string Link { get; set; }
private void OpenItem()
{
Debug.WriteLine(Link);
}
}
replace
p => OpenItem(_HOW_DO_I_GET_THE_PARAMETER?_)
with
p => OpenItem(p)
that is what the p stands for: parameter

updating text with filename with path via OpenFileDialog using MVVM and WPF

I have a text box for file name with path. After user locates a file using OpenFileDialog, this text box should be populated with filename. This text should also work when user enters filename with path directly instead of selecting from file dialog box.
Since I am learning MVVM, I am getting hard time to figure out how to update text box with filename/path. I tried everything i can think of.
I was expecting onPropertyChanged(“FilenameWithPath”) should take care this issue. Can somebody show me how to deal with this issue?
See code below
FileBrowseView.xaml
<TextBox Height="23" HorizontalAlignment="Left" Margin="113,22,0,0"
Name="txtFilenameWithPath"
Text="{Binding Path=FilenameWithPath,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
VerticalAlignment="Top" Width="300" />
<Button
Content="Browse..."
Height="30"
HorizontalAlignment="Left"
Margin="433,20,0,0"
Name="btnBrowse"
VerticalAlignment="Top"
Width="142"
Command="{Binding Path=BrowseCommand}" />
FileBrowseView.xaml.cs
public partial class FileBrowseView : Window
{
public FileBrowseView()
{
InitializeComponent();
DataContext = new FileBrowseViewModel();
}
}
FileBrowseModel
public class FileBrowseModel
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
}
}
}
FileBrowseViewModel
public class FileBrowseViewModel : INotifyPropertyChanged
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get
{
if (_browseCommand == null)
_browseCommand = new DoBrowse();
return _browseCommand;
}
set
{
_browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
private class DoBrowse : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
//I am trying to assign value i got from OpenFileDialog to
// FilenameWithPath property
//complier says "Cannot access non static member of outer type
'MyProject.FileBrowseViewModel' via
nested type 'MyProject.FileBrowseViewModel.DoBrowse
onPropertyChanged(“FilenameWithPath”);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You just need to set FileNameWithPath in your command's Execute function. And the setter for FileNameWithPath ought to be calling OnPropertyChanged. You shouldn't have to call that from your command's Execute function.
EDIT: Make sure that you are setting your data context to be the viewmodel and not the model since both have FilenameWithPath properties. If you were doing this the bind wouldn't fail because there is still a property to bind to. Otherwise:
Make the following changes:
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
{
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
}
and
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
}
This should fix your problem. Additionally, consider changing which dialog box you use since this is WPF (as suggested in my comment).
Finally I am able to resolve this issue by adding new class called RelayCommand. I have modified the get block of _browseCommand use relay command as below.
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get{
if (_browseCommand == null){
_browseCommand = new RelayCommand(
a => this.DoBrowseFolder(),
p => this.CheckCondition());
}
return _browseCommand;
}
set
{ _browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
public bool CheckCondition () {
//Check condition here if needed
return true;
}
private void DoBrowseFolder(){
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
OnPropertyChanged("FilenameWithPath ");
}
}
Relay Command Class
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
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
}

MVVM, Data binding command

I've started using databinding with MVVM pattern in Silverlight. There's one problem for me.
I have a listbox, when I select an item there one property in the View-Model have to change it's value. I implemented DelegateCommand and made custom CommandListBox class as follows
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T> _canExecute;
public DelegateCommand(Action<T> execute)
: this(execute, x => true)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
if (canExecute == null) throw new ArgumentNullException("canExecute");
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged.DynamicInvoke(this);//.Raise(this);
}
}
public class DelegateCommand : DelegateCommand<object>
{
public DelegateCommand(Action execute)
: base(execute != null ? x => execute() : (Action<object>)null)
{
}
public DelegateCommand(Action execute, Func<bool> canExecute)
: base(execute != null ? x => execute() : (Action<object>)null,
canExecute != null ? x => canExecute() : (Predicate<object>)null)
{
}
}
public class CommandListBox : ListBox
{
public CommandListBox()
{
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof(ICommand), typeof(CommandListBox),
new PropertyMetadata(null, CommandChanged));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
var treeList = source as CommandListBox;
if (treeList == null) return;
treeList.RegisterCommand(args.OldValue as ICommand, args.NewValue as ICommand);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
private void HandleCanExecuteChanged(object sender, EventArgs args)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object), typeof(CommandListBox),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
I'm catching the event, when the selected item in listbox is changed but I don't know how to take it's value.
Could you help me please?
Your CommandListBox derives from a ListBox, it exposes a SelectionChanged event, you're attaching to that event already.
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
In order to obtain the currently selected item you can do something like this :
SelectionChanged += (sender, e) =>
{
CommandListBox source = sender as CommandListBox; // This is the sender
if(source != null) // just to be sure
{
var value = source.SelectedItem;
}
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
Does this help ?
You can bind your listbox's SelectedItem property to an appropriate property in your ViewModel.

Resources