Reporting progress from ViewModel method executed in a Task - c#

i'm trying to report progress from ViewModel (MVVM Light) methods that are executed in a task. The ProgressViewModel contains a ProgressModel property that provides properties to describe the current state. Those properties are bound to an Xceed BusyIndicator. So far, so good. But its not working as expected. The ProgressModel.IsRunning is bound to BusyIndiciator.IsBusy and toggles the visibility - that works. ProgressModel.Description and ProgressModel.Percentage are bound to a TextBlock.Text/ProgressBar.Value - but they are not updated...
So... since IsRunning works, whats wrong with Description and Progress...?
// Model
public sealed class ProgressModel: ObservableObject
{
private string m_description;
private float m_percentage;
private bool m_running;
public string Description
{
get { return m_description; }
set { Set(ref m_description, value); }
}
public float Percentage
{
get { return m_percentage; }
set { Set(ref m_percentage, value); }
}
public bool IsRunning
{
get { return m_running; }
set{Set(ref m_running, value);}
}
public void Initiate()
{
Description = string.Empty;
Percentage = 0;
IsRunning = true;
}
}
// Command
private void DownloadWatch()
{
DispatcherHelper.UIDispatcher.Invoke(() =>
{
Progress.Initiate();
});
using (var watch = new PolarWatch())
{
watch.Connect();
for (var i = 0; i < watch.Sessions.Count; i++)
{
DispatcherHelper.UIDispatcher.Invoke(() =>
{
Progress.Description = $"Writing session data for '{session.DateTime}'...";
});
}
}
DispatcherHelper.UIDispatcher.Invoke(() =>
{
Progress.IsRunning = false;
});
}
// View
<xctk:BusyIndicator Name="Busy" IsBusy="{Binding Progress.IsRunning}">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<Grid Width="150">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Progress.Description}"/>
<ProgressBar Grid.Row="1" Grid.Column="0" Value="{Binding Progress.Percentage}" Height="14" Margin="0,5,0,0"/>
</Grid>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
<xctk:BusyIndicator.ProgressBarStyle>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</xctk:BusyIndicator.ProgressBarStyle>

The DataContext of the root element in the ItemTemplate is not the same as the DataContext of the control itself. Try this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding DataContext.Progress.Description, RelativeSource={RelativeSource AncestorType=xctk:BusyIndicator}}"/>
<ProgressBar Grid.Row="1" Grid.Column="0" Value="{Binding DataContext.Progress.Percentage,RelativeSource={RelativeSource AncestorType=xctk:BusyIndicator}}" Height="14" Margin="0,5,0,0"/>
Or set the DataContext of the Grid:
<Grid Width="150" DataContext="{Binding DataContext.Progress, RelativeSource={RelativeSource AncestorType=xctk:BusyIndicator}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Description}"/>
<ProgressBar Grid.Row="1" Grid.Column="0" Value="{Binding Percentage}" Height="14" Margin="0,5,0,0"/>
</Grid>

Related

Editing copies of an ObservableCollection's Object without editing the ObservableCollection

I have a WPF form that I want to use to manipulate a database table. To get the data out of the database I use Entity Framework Core. I try to follow the MVVM pattern.
So I have a Model called Employee with a number of string properties like Firstname and so on.
The form has a ListView that shows an ObservableCollection of Employee. Further I have a number of TextBoxes to display and manipulate the properties of single Employee objects. The selectedMemberPath of the ListView is bound to a property SelectedEmployee. The setter of SelectedEmployee than copies it's own properties to EditedEmployee. The TextBoxes are bound to EditedEmployee. The idea is, that I don't want to directly edit the ObservableCollection. This should only happen if a save button is clicked.
This is my view model.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using XdcCore.Models;
using XdcCore.ViewModels.Commands;
namespace XdcCore.ViewModels
{
class EmployeeEditorVM : List<Employee>, INotifyPropertyChanged
{
public EmployeeSaveCommand EmployeeSaveCommand { get; set; }
public EmployeeEditorVM()
{
EmployeeSearch();
EmployeeSaveCommand = new EmployeeSaveCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string employeeSearchText;
public string EmployeeSearchText
{
get { return employeeSearchText; }
set
{
employeeSearchText = value;
OnPropertyChanged("EmployeeSearch");
EmployeeSearch();
}
}
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
EditedEmployee = SelectedEmployee;
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employees;
public ObservableCollection<Employee> Employees
{
get { return employees; }
set
{
employees = value;
OnPropertyChanged("Employees");
}
}
public void EmployeeSearch()
{
using (var context = new XdcContext())
{
if (employeeSearchText != null)
{
var _employees = context.Employee
.Where(x => x.Firstname.Contains(employeeSearchText));
Employees = new ObservableCollection<Employee>(_employees);
}
else
{
var _employees = context.Employee.Select(x => x);
Employees = new ObservableCollection<Employee>(_employees);
}
}
}
public void EmployeeSave()
{
using (var context = new XdcContext())
{
var entity = context.Employee.First(x => x.IdEmployee == SelectedEmployee.IdEmployee);
{
entity.Firstname = SelectedEmployee.Firstname;
entity.Lastname = SelectedEmployee.Lastname;
entity.Initials = SelectedEmployee.Initials;
context.SaveChanges();
}
EmployeeSearch();
}
}
}
}
this is my View:
<Window
x:Class="XdcCore.Views.Editors.EmployeeEditor"
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:local="clr-namespace:XdcCore.Views.Editors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:XdcCore.ViewModels"
Title="Employee"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<vm:EmployeeEditorVM x:Key="vm" />
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="115*" />
<ColumnDefinition Width="277*" />
</Grid.ColumnDefinitions>
<!-- left column -->
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="aUTO" />
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Margin="3,3,3,3"
Content="Filter:" />
<TextBox
x:Name="TextBoxSearch"
Grid.Row="1"
Height="25"
Margin="3,0,3,3"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EmployeeSearchText, Mode=TwoWay}" />
<ListView
x:Name="ListViewOverview"
Grid.Row="2"
Margin="3,0,3,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedItem="{Binding SelectedEmployee, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Initials}" />
<TextBlock Text=" : " />
<TextBlock Text="{Binding Firstname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Lastname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonDelete"
Grid.Column="0"
Height="25"
Margin="3,3,3,3"
Content="Delete"
IsEnabled="False" />
<Button
x:Name="ButtonNew"
Grid.Column="1"
Height="25"
Margin="3,3,3,3"
Content="Add New ..."
IsEnabled="True" />
</Grid>
</Grid>
</Border>
<!-- right Column -->
<Grid Grid.Column="1" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- right column, first row -->
<Border
Grid.Row="0"
BorderBrush="DarkGray"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
Content="First Name" />
<Label
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Last Name" />
<Label
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Initials" />
<Label
Grid.Row="3"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="ID" />
<Label
Grid.Row="4"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCU" />
<Label
Grid.Row="5"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCT" />
<TextBox
x:Name="TextBoxFirstName"
Grid.Row="0"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Firstname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxLastName"
Grid.Row="1"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Lastname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxInitials"
Grid.Row="2"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Initials, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxId"
Grid.Row="3"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCU"
Grid.Row="4"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCT"
Grid.Row="5"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
</Grid>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButnSave"
Grid.Column="1"
Width="76"
Height="25"
Margin="60,3,60,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Command="{Binding EmployeeSaveCommand}"
Content="Save"
IsEnabled="True" />
</Grid>
<!-- Add new -->
</Grid>
</Grid>
</Grid>
</Window>
In other words: I want to click on an item in the list view, it's details are shown in the text boxes. I can change the text in the text boxes but the changes are not applied to the observable collection (or even database) until I click "save". The problem is, that exactly that happens: when I edit text in the text boxes the changes are also shown in the list view. And I can#t see why.
So, I got the concept of "copy vs point" of a variable wrong.
I had two solutions for my problem.
To my Employee model I added the method Copy:
public partial class Employee
{
public int IdEmployee { get; set; }
public string Initials { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Rcu { get; set; }
public DateTime Rct { get; set; }
public Employee Copy()
{
return (Employee)this.MemberwiseClone();
}
}
Then I Changed my ViewModel so that EditedEmployee is a copy of SelectedEmployee
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
if (SelectedEmployee != null)
{
EditedEmployee = SelectedEmployee.Copy();
}
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
The problem with this approach is, that if I mess around with the database and then need to run Scaffold-DbContext (I prefer the code-first approach of EFC) again, I would have to modify all models again. A solution to that might be some a second, modified Model that inherits from the original Model and then adds the Copy method.
My second approach is to bind the ListView via SelectedIndex instead of SelectedItem.
<ListView
...
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedIndex="{Binding SelectedItemIndex, Mode=TwoWay}">
In the ViewModel this is then represented by an int property. If this int is set (and >= 0) Employee gets copied EmployeeWorkingCopy. Furthermore if EmployeeWorkingCopy is set a single Employee dubbed EditedEmployee is set as the Employee of the working copy with the selected index.
private int selectedItemIndex;
public int SelectedItemIndex
{
get { return selectedItemIndex; }
set
{
selectedItemIndex = value;
if (SelectedItemIndex >= 0)
EmployeesWorkingCopy = new ObservableCollection<Employee>(Employees);
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employeesWorkingCopy;
public ObservableCollection<Employee> EmployeesWorkingCopy
{
get { return employeesWorkingCopy; }
set
{
employeesWorkingCopy = value;
EditedEmployee = EmployeesWorkingCopy[SelectedItemIndex];
}
}
Thanks a lot for pointing out my misconception.

Collection is not Filtering the List Items in WPF

I have a Observable Collection, I want to Filter the collection. The Collection class like a dictionary. I have tried the following code But it is not filtering the collection. I know it is very basic question. I am new to c#. I am studying. Please any one help me to filter the collection. I have mentioned the code below, please refer
GroupModel
public class GroupModel
{
public string Key { get; set; }
public ObservableCollection<ValueModel> Values { get; set; }
}
ValueModel
public class ValueModel
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel
Public Class ViewModel
{
public ObservableCollection<GroupModel> MainValues
{
get { return mainValues; }
set { mainValues = value; }
}
public string SearchIndicator
{
get { return searchIndicator; }
set
{
searchIndicator = value;
OnPropertyChanged(SearchIndicator);
ItemsView.Refresh();
}
}
public ViewModel(IMainValueCollection mainValueCollection)
{
MainValue = new ObservableCollection<GroupModel>();
this.MainValue = mainValueCollection;
ItemsView.Filter = new Predicate<object>(o => Filter(o as ValueModel));
}
public ICollectionView ItemsView
{
get
{
var value = (MainValues.SelectMany(a => a.Values)).ToList();
return CollectionViewSource.GetDefaultView((MainValues.SelectMany(a => a.Values)));
}
}
private bool Filter(ValueModel value)
{
if (SearchIndicator == null)
{
return true;
}
return value.Name.IndexOf(SearchIndicator, StringComparison.OrdinalIgnoreCase) != -1;
}
}
MainViewUI
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Height="35"
Margin="5,5,0,5">
<Border
Margin="10,10,10,0"
Padding="1"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="0">
<Grid
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="0"
Grid.Column="0"
Margin="2,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image
Width="25"
Height="25"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="/Images/SearchIcon.png" />
</Grid>
<Grid
Name="TextBlockGrid"
Grid.Row="0"
Grid.Column="1"
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Padding="5,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Foreground="Gray"
Text="Search">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource MultiStringToVisibilityConverter}">
<Binding ElementName="searchBox" Path="Text.IsEmpty" />
<Binding ElementName="searchBox" Path="IsFocused" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
<TextBox
x:Name="searchBox"
Width="{Binding ActualWidth, ElementName=TextBlockGrid}"
Padding="5,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Text="{Binding SearchIndicator, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
</Border>
</Grid>
<ItemsControl
Grid.Row="1"
MinWidth="150"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="White"
BorderThickness="0"
ItemsSource="{Binding MainValues, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<Grid>
<TextBlock x:Name="keys" Text="{Binding Key}" />
</Grid>
</Expander.Header>
<ListBox
Margin="15,0,0,0"
BorderThickness="0"
DisplayMemberPath="Name"
ItemsSource="{Binding Values}" />
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You are not binding to ItemsView at all.
Replace
ItemsSource="{Binding MainValues, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
with
ItemsSource="{Binding ItemsView}">
Also change the ItemsView implementation to
public ICollectionView ItemsView
{
get { return CollectionViewSource.GetDefaultView(MainValues); }
}

Problems with Visibility Binding in DataTemplate

I don't know why but my Visibility Binding isn't working ONLY in the DataTemplate. Did I forget something?
Edit: All Bindings (except for this one work perfectly)
Thats the structure.
<Window>
<Grid>
<Grid>
<Grid Grid.Row="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl x:Name="Targets" Margin="0,4,0,4">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" PreviewKeyDown="ChangeLocationAddress" PreviewGotKeyboardFocus="TxtGotKeyboardFocusHandler" LostKeyboardFocus="ChangeLocationAddress" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Click="MoveLocationUp" Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Click="MoveLocationDown" Visibility="{Binding Path = UpDownButtonVisibility, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="btnNewAddress" Grid.Row="1" Content="Neues Ziel hinzufügen" Margin="0,4,0,4" Visibility="{Binding Path=TargetButtonVisibility}"/>
</Grid>
</Grid></Grid></Grid></Window>
Codebehind:
public MapView(){
this.DataContext = this.ViewModel = new MapVM();
this.InitializeComponent();
this.Targest.Itemssource = this.ViewModel.ToLocations;
}
ViewModel:
public MapVM()
{ this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _UpDownButtonVisibility;
/// <summary>
/// Property Visibility für "↓" und "↑"
/// </summary>
public Visibility UpDownButtonVisibility
{
get { return _UpDownButtonVisibility; }
set
{
this._UpDownButtonVisibility = value;
NotifyPropertyChanged("UpDownButtonVisibility");
}
}
public Visibility TargetButtonVisibility { get; set; }
EDIT:
Program Output:
BindingExpression path error: 'UpDownButtonVisibility' property not found on 'object' ''Location' (HashCode=-794088449)'. BindingExpression:Path=UpDownButtonVisibility; DataItem='Location' (HashCode=-794088449); target element is 'Button' (Name=''); target property is 'Visibility' (type 'Visibility') 1.10s
Any suggestions?
I cannot find a PropertyChanged event handler and a call to it in your code. Add the INotifyPropertyChanged to your DataContext object and it should work
Personally I would model the visibility as a bool and use the BooleasnToVisibility Converter that comes with WPF.
Change string to Visibility
public Visibility UpDownButtonVisibility { get; set; }
this.UpDownButtonVisibility = Visibility.Collapsed;
Add INPC and binding to a View model.
Here is working sample:
XAML
<Window x:Class="ItemsControlDataTemplate.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:ItemsControlDataTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ItemsControl x:Name="Targets" Margin="0,4,0,4" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Visibility="{Binding Path=UpDownButtonVisibility}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
C#
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<MapVM> _items = new ObservableCollection<MapVM>();
public ObservableCollection<MapVM> Items { get { return _items; } }
public MainWindowViewModel()
{
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "1111111" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Collapsed, Address = "222222" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "33333333" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MapVM : INotifyPropertyChanged
{
public MapVM()
{
this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _upDownButtonVisibility;
public Visibility UpDownButtonVisibility
{
get { return _upDownButtonVisibility; }
set
{
_upDownButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UpDownButtonVisibility)));
}
}
private Visibility _targetButtonVisibility;
public Visibility TargetButtonVisibility
{
get { return _targetButtonVisibility; }
set
{
_targetButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TargetButtonVisibility)));
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Address)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The DataContext of your TargetButtonVisibility binding is your main MapVM. This works ok.
The DataContext within your DataTemplate is not MapVM, is it the item being displayed by the template.
Since you have not supplied any ItemsSource binding on your ItemsControl we have no way of knowing what this actually is.
Also, as unkreativ points out, do not use Visibility in your VM, since this is a view related type. Use bool instead and call the property IsUpDownButtonVisible or similar.
EDIT: Assuming you do actually want to bind to your single MapVM, you could use a RelativeSource to find the parent ItemsControl:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}"/>
However, since you've already named your ItemsControl (Targets), you can simply refer to it by name:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
ElementName=Targets}"/>

Trouble binding nested collections

I'm a beginner at WPF, and I'm trying to bind a nested collection.
I've found many topics online about binding, and I tried following this question/answer. I tried changing the Datacontext and ItemSource values, but I just can't seem to get it right.
XAML:
<UserControl x:Class="WpfApplication1.Nav1"
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"
mc:Ignorable="d"
d:DesignHeight="620" d:DesignWidth="950">
<Grid Background="#FF741125">
<TextBlock Height="61" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="BEVERAGES" VerticalAlignment="Top" Width="250" FontSize="20" FontWeight="Black" />
<ItemsControl x:Name="Stack" DataContext="{Binding myMenu}" ItemsSource="{Binding Subs}" BorderThickness="0" Margin="0,60,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" FontWeight="Bold" />
<ItemsControl ItemsSource="{Binding Nodes}" BorderThickness="0" Margin="0,60,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="60" Width="900">
<Grid.Background>
<ImageBrush ImageSource="/WpfApplication1;component/Images/MenuGrid.fw.png" />
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" Grid.ColumnSpan="14" FontWeight="Bold" />
<Label Content="{Binding Description}" Grid.Column="1" Grid.Row="2" Grid.RowSpan="3" Grid.ColumnSpan="14" />
<Label Content="{Binding Cost}" ContentStringFormat="{}${0}" Grid.Column="16" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Class:
public class Menu
{
public ObservableCollection<Category> Subs;
public Menu()
{
Subs = new ObservableCollection<Category>();
}
}
public class Category
{
public ObservableCollection<MenuItem> Nodes;
public Category()
{
Nodes = new ObservableCollection<MenuItem>();
}
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public Category(string name)
{
this.name = name;
Nodes = new ObservableCollection<MenuItem>();
}
}
public class MenuItem
{
public MenuItem()
{
}
public MenuItem(string name, string description, double cost)
{
this.itemName = name;
this.description = description;
this.cost = cost;
}
private string itemName;
public string ItemName
{
get { return this.itemName; }
set { this.itemName = value; }
}
private string description;
public string Description
{
get { return this.description; }
set { this.description = value; }
}
private double cost;
public double Cost
{
get { return this.cost; }
set { this.cost = value; }
}
}
CS:
public Nav1()
{
InitializeComponent();
// Init Model
Menu myMenu = new Menu();
myMenu.Subs.Add(new Category("Soft Drinks"));
myMenu.Subs.Add(new Category("Coffee"));
myMenu.Subs.Add(new Category("Premium"));
myMenu.Subs[0].Nodes.Add(new MenuItem("Pepsi", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Diet Pepsi", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("7Up", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Mug Root Beer", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Brisk Iced Tea", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Bottled Water", "Thirsty? Aquafina.", 2.75));
// Set DataContext for StackPanel
Stack.DataContext = myMenu.Subs;
}
It displays the category names (eg. "Soft Drinks" or "Coffee") but I can't display the individual items. Also, are there better ways to do this? As mentioned, I'm a beginner at WPF and there seem to be many interesting functions I'm missing.
Thanks for reading!
Using a DataTemplate does not allow for the using of nested items since it does not provide an ItemsSource property.
<DataTemplate>
<TextBlock Text={Binding MyTextValue}/>
</DataTemplate>
However, if you use a HierarchicalDataTemplate, you can use the ItemsSource property to define the next level of items.
<HierarchicalDataTemplate ItemsSource={Binding MyListOfLevelTwoItems}>
<TextBlock Text={Binding MyLevelOneValue}/>
</HierarchicalDataTemplate>
<DataTemplate DataType={x:Type MyLevelTwoItem}>
<TextBlock Text={Binding MyLevelTwoValue}/>
</DataTemplate>

Validating Listbox MVVM

I am using Caliburn.Micro to do convention based binding of the contents of my listbox from a BindableCollection in my ViewModel.
I want to perform Validation on it so that when there is no selection and the user exits the form they get that nice red line around the listbox with the error message that they have to select a value. So far Caliburn has worked great for textbox validation but I can't seem to get it to work on the listbox!
On a side note, is there any way to forcibly run a complete validation from the VM? Currently I am merely doing manual validation by checking the values and changing them so it triggers the Validator which is a bit backwards..
Here is the code for my view:
<UserControl x:Class="AddSessionDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<toolkit:BusyIndicator IsBusy="{Binding IsBusy}" BusyContent="{Binding IsBusyStatusDisplay}">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto" />
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5,9" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<TextBlock Text="Session Name" />
<TextBox x:Name="SessionName" Grid.Column="1" />
<TextBlock Text="Scenario" Grid.Row="1" />
<ListBox x:Name="Scenarios" Grid.Row="1" Grid.Column="1" DisplayMemberPath="Name" Margin="5" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Column="1" Grid.Row="3" Margin="5">
<Button x:Name="Cancel" Height="28" Margin="5" Content="Cancel" Padding="0,2" Width="75" />
<Button x:Name="OK" Height="28" Margin="5" Content="OK" Padding="0,2" Width="100" />
</StackPanel>
</Grid>
</toolkit:BusyIndicator>
Here is the (relevant) code for my viewmodel:
public BindableCollection<Scenario> Scenarios { get; set; }
[Required(ErrorMessage = "You must select a scenario")]
public Scenario SelectedScenario { get; set; }
private string _sessionName;
[Required(ErrorMessage="Session Name is invalid")]
public string SessionName
{
get { return _sessionName; }
set { _sessionName = value; NotifyOfPropertyChange(() => SessionName); }
}
public IEnumerable<IResult> OK()
{
if (SelectedScenario == null)
{
// This forces validation check, but doesnt show validation error on the view!
SelectedScenario = new Scenario();
SelectedScenario = null;
}
if (SessionName == null)
SessionName = "";
if (Validator.TryValidateObject(this, new ValidationContext(this), new List<ValidationResult>()))
{
IsBusy = true;
IsBusyStatusDisplay = "Creating Session...";
yield return new CreateSessionResult(SelectedScenario.Id, SessionName);
IsBusy = false;
yield return Close();
}
}

Categories

Resources