Disabling controls during processing a function - c#

I have problem with disabling controls in WPF Application during processing a function. It is simple app sending data via serial port. When port is "listening" (SerialPort.ReadChar();) I want all controls to go gray/disable them.
This way:
private void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
disabling seems to work only inside the function, so nothing actually happens in the window. When I remove enabling at the end of function all controls go gray, but not at the moment of *.IsEnabled = false instructions, but when the functions ends. Is there something I do wrong or everything is OK and this needs to be done in different way?

Welcome to StackOverflow !
Since your code is synchronous it is blocking, hence the behavior you get. There is also the need to consider using the Dispatcher but luckily in your case you haven't encountered such issue.
Suggestions:
use a ViewModel
bind to some properties in it to enable/disable your UI
doing so separates concerns and simplifies your thing in general
Example : a 5 second work that disables the UI (really simple !)
Points of interest in my code:
by putting all controls that must be disabled within a StackPanel and binding its IsEnabled property the model's IsAvailable property I effectively simplify this process
no controls are modified from code-behind
the view (your window) does nothing more than presenting, all your logic is in a model that is not tied to your window and can be reused somewhere else
XAML:
<Window x:Class="WpfApplication1.MainView"
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:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainView"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance wpfApplication1:MainViewModel,
d:IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button Command="{Binding DoSomeWork}" Content="Do some long work" />
<StackPanel IsEnabled="{Binding IsAvailable}">
<CheckBox Content="Test control 1" />
<RadioButton Content="Test control 2" />
</StackPanel>
<TextBlock Text="Overall progress:" />
<ProgressBar Height="10" Value="{Binding CurrentProgress}" />
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
// put classes shown below here
}
Your model :
internal class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
// set-up environment
DoSomeWork = new DelegateCommand(DoSomeWorkExecute, DoSomeWorkCanExecute);
IsAvailable = true;
}
public int CurrentProgress
{
get { return _currentProgress; }
set
{
_currentProgress = value;
OnPropertyChanged();
}
}
#region IsAvailable
private bool _isAvailable;
private int _currentProgress;
public bool IsAvailable
{
get { return _isAvailable; }
set
{
_isAvailable = value;
OnPropertyChanged();
}
}
#endregion
#region DoSomeWork
public DelegateCommand DoSomeWork { get; private set; }
private bool DoSomeWorkCanExecute(object arg)
{
return true;
}
private async void DoSomeWorkExecute(object o)
{
await Task.Run(() =>
{
IsAvailable = false;
var steps = 20;
var time = 5000;
var length = time/steps;
for (var i = 0; i < steps; i++)
{
Thread.Sleep(length);
var currentProgress = (int) (((((double) i + 1)*length)/time)*100);
CurrentProgress = currentProgress;
}
IsAvailable = true;
});
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And a trivial command base for DoSomeWork:
internal class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute)
: this(execute, s => true)
{
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
TODO
Get familiar with:
tasks and asynchronous programming
MVVM for separating concerns, I'd suggest Prism, some would consider this is overkill but it has a very good documentation, there are other players such as MVVM Light
Dispatcher as well
You will experience some pain with these concepts the first time, but over time you will find these are the way to go esp. with WPF.
If you are satisfied with my answer, mark it as the answer, otherwise if you need some clarification then add a comment below and either me or someone will try to help further.

Please, read the full answer Aybe provided. It's always good to follow best practices. But when it comes to small quick test projects, I believe that sometimes it might be an overkill.
If you need quick solution to this problem then you could try to use the following approach:
private async void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
await
Task.Factory.StartNew(
() =>
{
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
}
);
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
Note that assigning value to c variable happens in another thread.
I hope my answer is helpful.

Related

Progress-bar MVVM?

I am having a little bit of trouble getting a ProgressBar to work. When I start it up, nothing happens and I can't see why?
I thought that this would start the task worker.RunWorkerAsync();
The below example should be able to be copied and pasted into a new solution and be run for testing if needed.
The XAML,
<Grid Margin="20">
<ProgressBar
Height="60"
Minimum="0"
Maximum="100"
Value="{Binding Progress, Mode=OneWay}"
Visibility="{Binding ProgressVisibility}"/>
</Grid>
My code,
public partial class MainWindow : Window
{
public ProggressbarViewModel PsVm { get; set; }
public MainWindow()
{
InitializeComponent();
PsVm = new ProggressbarViewModel();
}
public class ProggressbarViewModel
{
public ProggressbarViewModel()
{
var worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
}
private int _progress;
public int Progress
{
get { return _progress; }
set
{
if (_progress == value) return;
_progress = value;
OnPropertyChanged();
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
_progress = i;
OnPropertyChanged();
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress = e.ProgressPercentage;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Any help would be much appreciated.
EDIT: The question is is similar possibly a duplicate in that sense, however the linked answer did not solve my problem, like it states in the Duplicate banner.
When you're not explicitly indicating source object for your bindings (by means of Binding.Source or Binding.RelativeSource properties), the framework uses (possibly inherited) value of DataContext of the target object as the source. The problem is that you don't assign your view-model to the DataContext property of any control. Thus, the source for the bindings resolves to null and nothing is showing on your progress bar.
To resolve your issue you should assign your view model to the DataContext of your MainWindow:
public MainWindow()
{
InitializeComponent();
PsVm = new ProggressbarViewModel();
DataContext = PsVm;
}
If however you're planning on using different DataContext for your window, you can bind DataContext of ProgressBar:
<ProgressBar
DataContext="{Binding Path=PsVm,
RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
(...) />
You could also modify particular bindings by prepending PsVm. to the value of Path and using RelativeSource, e.g.:
Value="{Binding Path=PsVm.Progress,
RelativeSource={RelativeSource AncestorType=local:MainWindow},
Mode=OneWay}"
In that case however you'd have to modify each binding, so previous solutions are quicker and/or simpler.
As a side note, you might also want to change the way you're reporting progress - note that OnPropertyChanged in your DoWork method is not called from UI thread. The proper way to do it would be:
private void DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
worker.ReportProgress(i); //This will raise BackgroundWorker.ProgressChanged
}
}
Also, in order to support progress reporting, you should set WorkerReportsProgress to true on your worker, e.g.:
var worker = new BackgroundWorker { WorkerReportsProgress = true };

MVVM ICommand.CanExecute parameter contains previous value

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

WPF frame content not updating when source Uri is changed through binding

I've been working on this issue for a few days and can't seem to find anything that will work for my application.
My issue is that I am trying to use a User Control containing buttons to bind to commands which change the source Uri of a frame (both displaying in the same window). When I click a button it is changing the Uri within the ViewModel but the frame does not change the page to reflect this. I believe that it is either not picking up the change due to the way it is binding or there is something blocking it from changing the page which is displaying in the frame.
I am using the MVVM pattern which has been great until I reached the point that I had to start dealing with navigation. Any help would be greatly appreciated!
Navigation User Control View Buttons:
<Button Name="BtnMainDash" Content="Main Dashboard" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="180" Command="{Binding MainDashboard}"/>
<Button Name="BtnAccount" Content="Account" HorizontalAlignment="Left" Margin="10,40,0,0" VerticalAlignment="Top" Width="180" Command="{Binding EditAccount}"/>
<Button Name="BtnProjects" Content="Projects" HorizontalAlignment="Left" Margin="10,70,0,0" VerticalAlignment="Top" Width="180" Command="{Binding ProjectScreen}"/>
Main Window Frame:
<Frame x:Name="FmePages" Margin="200,30,-0.4,0.4"
Source="{Binding Path=CurrentPage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged }"
NavigationUIVisibility="Hidden"/>
Button ICommands (All the same except that each calls a difference Uri changing command):
using System;
using System.Windows.Input;
using ScrumManagementApplication.Pages.MainWindow.ViewModel;
namespace ScrumManagementApplication.Pages.MainWindow.Commands
{
class LoadEditAccount : ICommand
{
private readonly NavigationViewModel _navigationViewModel;
public LoadEditAccount(NavigationViewModel navigationViewModel)
{
_navigationViewModel = navigationViewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _navigationViewModel.CommandsEnabled;
}
public void Execute(object parameter)
{
_navigationViewModel.LoadEditAccount();
}
}
}
ViewModel:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using ScrumManagementApplication.Pages.MainWindow.Commands;
using ScrumManagementApplication.SessionData;
using MessageBox = System.Windows.MessageBox;
namespace ScrumManagementApplication.Pages.MainWindow.ViewModel
{
public class NavigationViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
public bool CommandsEnabled = false;
public NavigationViewModel()
{
MainDashboard = new LoadMainDashboard(this);
EditAccount = new LoadEditAccount(this);
ProjectScreen = new LoadProjectScreen(this);
LogOut = new LoadLogOut(this);
CommandsEnabled = true;
LoadEditAccount();
}
#region ICommands
public ICommand MainDashboard { get; private set; }
public void LoadMainDashboard()
{
_currentPage = null;
_currentPage = new Uri("pack://application:,,,/Pages/MainWindow/View/MainDashboardView.xaml", UriKind.Absolute);
}
public ICommand EditAccount { get; private set; }
public void LoadEditAccount()
{
_currentPage = null;
_currentPage = new Uri("pack://application:,,,/Pages/EditUserDetailsPage/View/EditUserDetailsView.xaml", UriKind.Absolute);
}
public ICommand ProjectScreen { get; private set; }
public void LoadProjectScreen()
{
_currentPage = null;
_currentPage = new Uri("pack://application:,,,/Pages/ProjCreationPage/View/ProjectCreationPage.xaml", UriKind.Absolute);
}
public ICommand LogOut { get; private set; }
public void LoadLogOut()
{
var dialogResult = MessageBox.Show("Are you sure you want to log out?", "Log Out", MessageBoxButton.YesNo);
if (dialogResult == (MessageBoxResult) DialogResult.Yes)
{
App.Current.Shutdown();
}
}
#endregion // ICommands
#region MainFrame
private Uri _currentPage;
public Uri CurrentPage
{
get { return _currentPage; }
set
{
_currentPage = value;
OnPropertyChanged("CurrentPage");
}
}
#endregion // MainFrame
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion // INotifyPropertyChanged Members
public event PropertyChangingEventHandler PropertyChanging;
protected void OnPropertyChanging(String propertyName)
{
PropertyChangingEventHandler handler = PropertyChanging;
if (handler != null)
{
handler(this, new PropertyChangingEventArgs(propertyName));
}
}
}
}
Any & all help appreciated, even if it doesn't fully solve my issue anything that helps me get closer to a solution is good!
Thanks in advance
Instead of doing this
_currentPage = //some value;
do this:
CurrentPage = //some value
The event will be raised when you change the property and not the backing field.
EDIT
One more suggestion is to create a single command class since all your commands are setting a string value to a property. You can get the button name using the CommandParameter. Based on that set the Uri.
Got a similar Problem, had a mainwindow with a frame in it and when I, for example, opened an openfiledialog and closed it, this frame (some others too) did not update even if I called PropertyChanged() and the bound Property had the right value. The only solution for me was to remove the binding and handle the Content with NavigationService instead. If you need this Property you bound to for some other approaches you could do something like:
private Page _dataExplorerContent;
public Page DataExplorerContent
{
get { return _dataExplorerContent; }
set
{
if(value != null)
{
contentFrame.Navigate(value)
SetField(ref _dataExplorerContent, value);
}
}
}

MVVM Binding Issue - Another Noob-ish Issue

So here I am again, asking a very similar question to yesterday. I re-factored my project in order to better follow the MVVM pattern. Now my binding is no longer working as it was yesterday. I am trying to bind the visibility of a dock panel to a button. Here is some of my code:
ViewModel:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
XAML:
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
<local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
<local:WaferTrackerWindowViewModel x:Key="WindowViewModel" />
</Window.Resources>
<DockPanel
Name="tvwDockPanel"
DataContext="{StaticResource SelectWaferButton}"
Width="225"
Visibility="{Binding IsControlVisible, Mode=TwoWay,
FallbackValue=Collapsed,
Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
</DockPanel>
My BoolToVisConverter:
public class BoolToVisibilityConverter : IValueConverter
{
public BoolToVisibilityConverter() { }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = (bool) value;
if (bValue)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Visibility visibility = (Visibility) value;
if (visibility == Visibility.Visible)
{
return true;
}
else
{
return false;
}
}
}
I apologize for a question that is similar to yesterday, but I am struggling with this MVVM stuff since I am quite new to WPF. Any help will be much appreciated.
Thanks in advanced,
EDIT:
Here is some extra code snippets for further reference:
public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
private SelectWaferButtonViewModel btnSelectWaferViewModel;
public event PropertyChangedEventHandler PropertyChanged;
private DelegateCommand exitCommand;
private DelegateCommand expandPanelCommand;
private DelegateCommand selectWaferCommand;
public WaferTrackerWindowViewModel()
{
this.InstantiateObjects();
initThread.RunWorkerAsync();
}
public string SelectedWafer
{
get
{
return selectedWafer;
}
set
{
selectedWafer = value;
}
}
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
public ICommand ExpandPanelCommand
{
get
{
if (expandPanelCommand == null)
{
expandPanelCommand = new DelegateCommand(ExpandPanel);
}
return expandPanelCommand;
}
}
public ICommand SelectWaferCommand
{
get
{
if (selectWaferCommand == null)
{
selectWaferCommand = new DelegateCommand(SelectWafer);
}
return selectWaferCommand;
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
initThread = new BackgroundWorker();
}
private void ExpandPanel()
{
btnSelectWaferViewModel.OnButtonClick();
}
private void SelectWafer()
{
//Does Nothing Yet
}
private void Exit()
{
Application.Current.Shutdown();
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void InitThread_DoWork(object sender, DoWorkEventArgs e)
{
TreeViewPresenter tvwPresenter = new TreeViewPresenter();
tvwPresenter.WaferList = DataLibrary.GetWaferList();
}
private void InitThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
tvwPresenter.TreeView.DataContext = tvwPresenter.ProcessesAndWafers;
tvwPresenter.WaferListCache = tvwPresenter.ProcessesAndWafers;
tvwPresenter.ProcessArray = tvwPresenter.WaferListCache.ToArray();
}
}
When the "expand panel" button gets clicked, it calls the ExpandPanel command, which routes the execution to the method "private void ExpandPanel()" in this same class. Then, in the ExpandPanel() method, it calls the OnButtonClick() method on the btnSelectWaferViewModel object, which will change the IsControlVisible property. This change should then be reflected onto the bound dock panel, but this is not happening
Kyle
(1) ViewModel should be in the Window.DataContext section, not the Window.Resources section.
(2) In your view model, make your IsControlVisible property a System.Windows.Visibility, rather than a Boolean, then you don't need a converter.
(3) I don't see any way for OnButtonClick to fire, and it really needs to be set up with ICommand interface.
(4) You don't need to implement ConvertBack because the Visibility property you're binding to is one way by definition. There is no way for the user to set the visibility to false.
(5) Don't mix accessing IsClicked and it's accessor IsControlVisible. Always use the Accessor in MVVM, because you run the risk of accidentally setting IsClicked which won't activate OnPropertyChanged.
All in all, you're pretty close. Make sure to keep an eye on your "Output" window, it will tell you if a binding is failing for some reason. But yeah, hang in there!
So when you do this:
<Window.Resources>
<local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
</Window.Resources>
WPF will create a new instance of the SelectWaferButtonViewModel and add it to it's resources. You then bind to this by setting the DataContext using the StaticResource with the key.
However, if you are then creating another SelectWaferButtonViewModel in your code behind and linking up your command to that instance, then it's not the same instance, so changes to the properties of this unbound instance won't effect your UI. There are a couple of ways around it. You can either a) create a single SelectWaferButtonViewModel in the code behind as a property and then bind to that in XAML, or b) Declare your SelectWaferButtonViewModel in XAML as you currently have it and then retrieve that instance in your code behind, like this:
SelectWaferButtonViewModel swbvm = (SelectWaferButtonViewModel)this.FindResource("SelectWaferButton");
Edit: So after seeing your last edit, if you want to go with a) then I would suggest you expose btnSelectWaferViewModel as a property in your WaferTrackerWindowViewModel and then bind to that property with the DataContext of your Window set to the WaferTrackerWindowViewModel instance. So you end up with something like:
<DockPanel
Name="tvwDockPanel"
Width="225"
Visibility="{Binding MyButton.IsControlVisible,
Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
</DockPanel>
and:
public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
private SelectWaferButtonViewModel btnSelectWaferViewModel;
public SelectWaferButtonViewModel MyButton
{
get { return btnSelectWaferViewModel; }
set
{
btnSelectWaferViewModel = value;
OnPropertyChanged("MyButton");
}
}
//......

MVVM updating Label

I know this is a frequently asked question, but I'm trying to solve it at least a week now... Read so many Threads, downloaded millions of different MVVM-Pattern-Examples and so on...
I just want to update a stupid label in my MVVM modelview first approach:
void StartUpProcess_DoWork(object sender, DoWorkEventArgs e)
{
SplashWindow splash = new SplashWindow();
var ViewModel_Splash = new VM_SplashWindow();
splash.DataContext = ViewModel_Splash;
splash.Topmost = true;
splash.Show();
ViewModel_Splash.DoWork();
}
The complete ViewModel:
public class VM_SplashWindow:VM_Base
{
#region Properties
private string _TextMessage;
public string TextMessage
{
get
{
return _TextMessage;
}
set
{
if(_TextMessage != value)
{
_TextMessage = value;
base.OnPropertyChanged("TextMessage");
}
}
}
#endregion
#region Methods
public void DoWork()
{
this.TextMessage = "Initialize";
for(int aa = 0; aa < 1000; aa++)
{
this.TextMessage = "Load Modul: " + aa.ToString();
Thread.Sleep(5);
}
this.TextMessage = "Done";
Thread.Sleep(1000);
}
#endregion
}
A small piece from the base:
public abstract class VM_Base:INotifyPropertyChanged, IDisposable
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
And finally the view:
<Label Height="28" Margin="19,0,17,15" Name="label2" VerticalAlignment="Bottom"
Content="{Binding Path=TextMessage}" Foreground="White" />
If I set a initial value for the TextMessage Property in the constructor of my viewmodel, this initial value will be shown after the splash.Show() command.
Setting the TextMessage Property in the DoWork-Method raises the onPropertyChangedEvent but unfortunately it will not update the label in the window. I don't know what I should do... I'm really looking forward for help. Many thanks in advance!
maybe I should mention that the StartUpProcess_DoWork is running in a own STAThread
kind regards, flo
Apparently, you are performing a lot of work in the GUI thread. And with Thread.Sleep you even suspend the GUI thread. Therefore, it will not be able to update the controls.
The solution is to use a different thread for the DoWork method. This can be easily achieved with a BackgroundWorker. If you provide the GUI dispatcher object to the worker, you can issue GUI changes from there. Although it would be better to use the ProgressChanged-Event for that, if it is possible.

Categories

Resources