My specific problem at the moment is I am trying to update a progress bar by binding it to a method which returns an int.
I'm trying to make a basic Microsoft Project style task manager in WPF as a learning exercise. I'm very new to C#, WPF and the MVVM pattern. I'm adding 'Tasks' to a tree view with an 'Add' button that creates children for that 'Task'. I then want to be able to put in numbers for 'Total Hours' and 'Hours Remaining' inside Text Boxes and then see the percentage of the task done in a progress bar. At the moment when I put in numbers nothing is happening and I really don't know what I'm doing.
MainWindow.xaml
<Window x:Class="Project_Management_App___Test_02.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:Project_Management_App___Test_02"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
DataContext="{Binding RelativeSource={RelativeSource self}}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="New" Click="MenuItem_Click" />
<MenuItem Header="Open" />
<MenuItem Header="Save" />
<Separator />
<MenuItem Header="Exit" />
</MenuItem>
</Menu>
</DockPanel>
<Grid>
<TreeView Name="treeview" Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Task}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<Label>Task Name</Label>
<TextBox Width="120" VerticalAlignment="Center" Text="Text Goes here"></TextBox>
<ProgressBar Width="200" Height="10" Value="{Binding Path=GetProgressPercentage}" Margin="4"></ProgressBar>
<Label>Hours Total</Label>
<TextBox Width="30" VerticalAlignment="Center" Text="{Binding Path=HrsTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<Label>Hours Remaining</Label>
<TextBox Width="30" VerticalAlignment="Center" Text="{Binding HrsRemaining, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Margin="4" Width="20" Command="{Binding Path=AddBtnClick}" >+</Button>
<Button Margin="4" Width="20" Command="{Binding Path=DelBtnClick}" >-</Button>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</StackPanel>
</Window>
Task.cs
class Task : TreeViewItemBase
{
public Task()
{
this.Items = new ObservableCollection<Task>();
}
public ObservableCollection<Task> Items { get; set; }
public ObservableCollection<Task> ParentItems { get; set; }
private int _hrsTotal;
private int _hrsRemaining;
public int HrsTotal
{
get { return _hrsTotal; }
set
{
if (_hrsTotal != value)
{
_hrsTotal = value;
this.NotifyPropertyChanged("HrsTotal");
}
}
}
public int HrsRemaining
{
get { return _hrsRemaining; }
set
{
if (_hrsRemaining != value)
{
_hrsRemaining = value;
this.NotifyPropertyChanged("HrsRemaining");
}
}
}
public int GetProgressPercentage()
{
if(this.HrsTotal != 0)
{
return 100 -((this.HrsRemaining / this.HrsTotal) * 100);
}
else { return 0; }
}
private ICommand _addBtnClick;
private ICommand _delBtnClick;
public ICommand AddBtnClick
{
get
{
if(_addBtnClick == null)
{
_addBtnClick = new RelayCommand(param => this.AddNewTask(), param => this.CanAddTask());
}
return _addBtnClick;
}
}
public ICommand DelBtnClick
{
get
{
if(_delBtnClick == null)
{
_delBtnClick = new RelayCommand(param => this.DeleteTask(), param => this.CanDelTask());
}
return _delBtnClick;
}
}
private bool CanAddTask()
{
return true;
}
private bool CanDelTask()
{
if(ParentItems != null) { return true; }
else { return false; }
}
public void AddNewTask()
{
Task newTask = new Task();
newTask.ParentItems = this.Items;
this.Items.Add(newTask);
this.IsExpanded = true;
}
public void DeleteTask()
{
ParentItems.Remove(this);
}
}
TreeViewItemBase.cs
class TreeViewItemBase : INotifyPropertyChanged
{
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
if(value != this.isExpanded)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
RelayCommand.cs
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 event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
I am expecting to put in a number for the 'Total hours' Textbox then put in a number for the 'Hours remaining' Textbox and the see the percentage bar reflect the progress.
You do not bind to methods. You bind to properties. When properties raise the INotifyPropertyChanged.PropertyChanged
event the binding gets updated automatically.
you could use:
public int ProgressPercentage
{
get
{
if(this.HrsTotal != 0)
{
return 100 -((this.HrsRemaining / this.HrsTotal) * 100);
}
else { return 0; }
}
}
and call
OnPropertyChanged(nameof(ProgressPercentage))
when HrsRemaining
or HrsTotal
have changed.