Bind WPF Command to ViewModel property in C# XAML - c#

I'm trying to build a generic Command that can access properties from my ViewModel. On my Window are several TextBoxes, each TextBoxhas a Button next to it. When the Button is clicked I show an OpenFileDialog and set the Text of the TextBox to the selected files path. The TextBoxitself has a Binding to a property in the ViewModel. Currently this is implemented with a Command in the ViewModel. The Buttons all call the same Commandbut each Button has a its property CommandParameter set to the TextBox that will receive the filepath. The drawback of this is, that I need to cast the parameter from the Commandexecution to a TextBox and then set its Textproperty. My question is now, if I can't somehow bind my 'Command' to the same property the TextBoxis bound to. Here is what I currently do:
XAML
<TextBox Text="{Binding SettingsPath}" x:Name="txtSettingsPath"></TextBox>
<Button Command="{Binding OpenFile}"
CommandParameter="{Binding ElementName=txtSettingsPath}">...</Button>
C#
public ICommand OpenFile
{
get
{
bool CanExecuteOpenFileCommand()
{
return true;
}
CommandHandler GetOpenFileCommand()
{
return new CommandHandler((o) =>
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
if (!string.IsNullOrEmpty(SettingsPath) && File.Exists(settingsPath))
{
ofd.InitialDirectory = Path.GetDirectoryName(SettingsPath);
}
if(ofd.ShowDialog() == true)
{
if(o is TextBox txt)
{
txt.Text = ofd.FileName;
}
}
}, CanExecuteOpenFileCommand);
}
return GetOpenFileCommand();
}
}
In the XAML I would like to have something like this:
<TextBox Text="{Binding SettingsPath}"></TextBox>
<Button Command="{Binding OpenFile}"
CommandParameter="{Binding SettingsPath}">...</Button>

Here's what I was talking about in comments:
The "little viewmodel". I added a Label property because in my test project, they all looked the same. That doesn't have to be part of this viewmodel.
public class SettingsPathSelectorViewModel : ViewModelBase
{
#region Label Property
private String _label = default(String);
public String Label
{
get { return _label; }
set
{
if (value != _label)
{
_label = value;
OnPropertyChanged();
}
}
}
#endregion Label Property
#region SettingsPath Property
private String _settingsPath = null;
public String SettingsPath
{
get { return _settingsPath; }
set
{
if (value != _settingsPath)
{
_settingsPath = value;
OnPropertyChanged();
}
}
}
#endregion SettingsPath Property
public ICommand OpenFile
{
get
{
bool CanExecuteOpenFileCommand()
{
return true;
}
// We're no longer using the parameter, since we now have one
// command per SettingsPath.
CommandHandler GetOpenFileCommand()
{
return new CommandHandler((o) =>
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
if (!string.IsNullOrEmpty(SettingsPath) && System.IO.File.Exists(SettingsPath))
{
ofd.InitialDirectory = System.IO.Path.GetDirectoryName(SettingsPath);
}
if (ofd.ShowDialog() == true)
{
SettingsPath = ofd.FileName;
}
}, o => CanExecuteOpenFileCommand());
}
return GetOpenFileCommand();
}
}
}
A quickie main viewmodel for demo purposes. We'll illustrate two different ways you could expose these things: Either as named properties, or a collection of varying size displayed in an ItemsControl.
public class MainViewModel : ViewModelBase
{
public SettingsPathSelectorViewModel FirstPath { get; } = new SettingsPathSelectorViewModel() { Label = "First Path" };
public SettingsPathSelectorViewModel SecondPath { get; } = new SettingsPathSelectorViewModel() { Label = "Second Path" };
public ObservableCollection<SettingsPathSelectorViewModel> SettingsPaths { get; } = new ObservableCollection<SettingsPathSelectorViewModel>
{
new SettingsPathSelectorViewModel() { Label = "First Collection Path" },
new SettingsPathSelectorViewModel() { Label = "Second Collection Path" },
new SettingsPathSelectorViewModel() { Label = "Third Collection Path" },
};
}
XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
<!-- GroupBox and Label are optional -->
<GroupBox Header="{Binding Label}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SettingsPath}" />
<Button
Content="..."
Command="{Binding OpenFile}"
HorizontalAlignment="Left"
MinWidth="40"
Margin="4,0,0,0"
/>
</StackPanel>
</GroupBox>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding FirstPath}" />
<ContentControl Content="{Binding SecondPath}" />
</StackPanel>
<ItemsControl
ItemsSource="{Binding SettingsPaths}"
/>
</StackPanel>
</Grid>
Here's what I mean about omitting Label and the GroupBox:
<Window.Resources>
<DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SettingsPath}" />
<Button
Content="..."
Command="{Binding OpenFile}"
HorizontalAlignment="Left"
MinWidth="40"
Margin="4,0,0,0"
/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Label>First Path</Label>
<ContentControl Content="{Binding FirstPath}" />
<Label>Second Path</Label>
<ContentControl Content="{Binding SecondPath}" />
</StackPanel>
</Grid>

Related

clickable button to get data wpf mvvm

View.Xaml
<Grid>
<ListView ItemsSource = "{Binding Path = dcCategory}" SelectedValuePath = "Key" SelectedValue = "{Binding Path = Category, Mode = TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" >
<Button Content="Add Value" Command="{Binding Path=DataContext.AddValue, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
<TextBlock Text="{Binding Path=Key.Name}"/>
</StackPanel>
<ListBox ItemsSource="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
My goal is to click Add Value and send selected item (Category type). Its right now it's working but not as I acepted.
Insted of clicking only button, I have to click first blue area and then code 'catch' the 'Category' with data. Otherwise Category is null.
example
ViewModel
private Category _Category;
public Category Category
{
get
{
return _Category;
}
set
{
if (_Category != value)
{
_Category = value;
OnPropertyChanged(() => Category);
}
}
}
public ICommand AddValue
{
get
{
if (_AddValue == null)
{
_AddValue = new BaseCommand(() => Messenger.Default.Send(CategoryValueCode.AddValue + "," + Category.CategoryId));
}
return _AddValue;
}
}
That's logic, because your button's command will be executed before ListView.SelectedValue is set. You can change it, if you handle PreviewMouseDown for the Button. I also found it better to set ListView.SelectionMode to Single.
<ListView ItemsSource = "{Binding Path = dcCategory}" SelectedValuePath = "Key" SelectedValue = "{Binding Path = Category, Mode = TwoWay}" SelectionMode="Single">
<Button Content="Add Value" Command="{Binding Path=DataContext.AddValue, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" PreviewMouseDown="PreviewMouseDown"/>
private void PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ListViewItem lvi = null;
var visParent = VisualTreeHelper.GetParent(sender as FrameworkElement);
while (lvi == null && visParent != null)
{
lvi = visParent as ListViewItem;
visParent = VisualTreeHelper.GetParent(visParent);
}
if (lvi == null) { return; }
lvi.IsSelected = true;
}
I didn't check Rekshino solution
Thank you for the tips, in the meantime during fighting with the problem I made so many changes that completely change viewmodel / view.
I achieved my goal in that way:
View:
<Grid>
<ItemsControl ItemsSource = "{Binding listCategoryAddValue}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add Value" Command="{Binding Path=AddValue}"/>
<TextBlock Text="{Binding Category.Name}"/>
</StackPanel>
<ListBox ItemsSource="{Binding ValueList}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
ViewModel:
public class CategoriesViewModel : WszystkieViewModel<CategoryAddValue>
{
#region Fields & Properties
private ObservableCollection<CategoryAddValue> _listCategoryAddValue;
public ObservableCollection<CategoryAddValue> listCategoryAddValue
{
get
{
if (_listCategoryAddValue == null) { Load(); }
return _listCategoryAddValue;
}
set
{
if (_listCategoryAddValue != value)
{
_listCategoryAddValue = value;
OnPropertyChanged(() => listCategoryAddValue);
}
}
}
#endregion Fields & Properties
#region Constructor
public CategoriesViewModel() : base()
{
base.DisplayName = "Kategorie";
}
#endregion Constructor
#region Helpers
private void SendValue(int CategoryId)
{
Messenger.Default.Send(CategoryValueCode.AddValue + "," + CategoryId);
}
public override void Load()
{
var allCategories = (from k in db.Category select k).ToList();
_listCategoryAddValue = new ObservableCollection<CategoryAddValue>();
foreach (var i in allCategories)
{
_listCategoryAddValue.Add(new CategoryAddValue(new RelayCommand(() => SendValue(i.KategoriaId)))
{
Category = i,
ValueList = db.CategoryValue.Where(x => x.CategoryId== i.CategoryId).Select(x => x.Value).ToList()
});
}
}
#endregion Helpers
}
Model
public class CategoryAddValue
{
public Category Category { get; set; }
public List<string> ValueList { get; set; }
private ICommand _addValue;
public ICommand AddValue
{
get
{
return _addValue;
}
}
public CategoryAddValue(RelayCommand command)
{
_addValue = command;
}
}

Data Binding does not Update on Property Change (UWP)

I am developing an application using c# and the Universal Windows Platform (UWP) and am struggling with creating a one-way data-bind between a layout control and an observable class. Currently, when the observable class property is changed, it does not update the UI element. I think it has something to do with the fact that I am binding a DataTemplate ListViewItem rather than a static layout element, but I am not sure if this is the problem or how to solve it. Any help would be appreciated. The code for the UI element and backend code is shown.
DataTemplate (XAML) (Styling is removed for readability)
<DataTemplate x:Key="variableTemplate"
x:DataType="local:VariableNode">
<Border>
<StackPanel Orientation="Vertical">
<Border>
<Grid>
<TextBlock Text="{Binding Name}" />
<StackPanel Orientation="Horizontal" >
<Button Tag="{Binding Description}"/>
<Button Tag="{Binding}"/>
</StackPanel>
</Grid>
</Border>
<Grid Margin="0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border >
<Grid Grid.Column="0">
<Button Click="Choose_Measurement"
Tag="{Binding}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Measurement_Name, Mode=TwoWay}"
Foreground="{x:Bind MF}" />
<TextBlock Foreground="{x:Bind MF}" />
</StackPanel>
</Button>
</Grid>
</Border>
<Grid Grid.Column="1">
<Button Foreground="{Binding UF}"
Tag="{Binding}"
IsEnabled="{Binding Unit_Exists}"
Click="Choose_Unit">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Unit_Name, Mode=OneWay}"
Foreground="{Binding UF}" />
<TextBlock Foreground="{Binding UF}" />
</StackPanel>
</Button>
</Grid>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
C# Observable Class VariableNode (Irrelevant properties removed)
public class VariableNode : ExperimentNode
{
public VariableNode() { }
public VariableNode(VariableType type)
{
Type = type;
Name = name_ref[(int)Type];
Category = "Problem";
Unit = -1;
}
private string[] name_ref = { "Independent Variable", "Dependent Variable", "Controlled Variable" };
public enum VariableType { Independent, Dependent, Controlled };
public VariableType Type { get; set; }
public Measurement Measure { get; set; }
public int Unit { get; set; }
[XmlIgnoreAttribute]
public Measurement MeasureSource
{
get { return this.Measure; }
set
{
this.Measure = value;
OnPropertyChanged("Measurement_Name");
}
}
[XmlIgnoreAttribute]
public string Measurement_Name
{
get
{
if (Measure == null) { return "Select a Measurement"; }
else { return Measure.Name; }
}
set
{
if (Measure != null)
{
Measure.Name = value;
OnPropertyChanged();
}
}
}
[XmlIgnoreAttribute]
public string Unit_Name
{
get
{
if (Measure == null) { return "No measurement"; }
else if (Unit < 0) { return "Select a unit"; }
else { return Measure.Unit[Unit]; }
}
}
[XmlIgnoreAttribute]
public bool Unit_Exists
{
get { return Measure != null; }
}
}
C# XAML.CS code calling the property change
public void Choose_Measurement (object sender, RoutedEventArgs e)
{
Button butt = sender as Button
VariableNode sel = butt.Tag as VariableNode;
sel.Measurement_Name = "New Name";
}
Again thanks for the help, I know its a lot of code, and I appreciate the help in debugging / learning.
Ok, so I ended up finding the answer, and I think that it may help others trying to replicate what I am trying to do:
Basically, the class that one is trying to make observable must extend the class INotifyPropertyChanged. So, I ended up making a base class from which to extend all of my observable classes from:
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}

How to bind events and properties of controls in UniformGrid dynamically?

I am a newbie in templating Wpf controls. I use VS2013, WPF 4.5 and Caliburn Micro 2.0.2. In part of tasks I have I need to populate a grid with toggle buttons contained different images and its subtitle. I have solved it using UniformGrid. See my code below. They work but still don't have event and property binding since I don't know how I can bind the events and properties of toggle buttons to view model, since they are generated automatically and dynamically and the number of toggle buttons is uncertain (depends on the number of images in the image folder).
For example:
manually I could bind the Click event, IsChecked property and some other properties of toggle button 1 like following:
<ToggleButton x:Name="ToggleVehicle01" IsChecked={Binding SelectedVehicle01} Background="{Binding BackColorSelectedVehicle01}" ToolTip="{Binding VehicleName01}">
But now I can't do that anymore since the toggle buttons are generated automatically and their number is uncertain. Please help. Feel free to change my code below or give me examples code that works. Thank you in advance.
The View (MainView.xaml):
<UserControl x:Class="CMWpf02.Views.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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="1024"
Height="768"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ShowGridLines="True">
<ItemsControl Name="ImageList"
Background="#FFFFFFFF"
BorderBrush="#FFA90606"
ItemsSource="{Binding Path=VehicleImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Margin="0,0,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="180"
Margin="10,10,10,10"
FontSize="10"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<!-- x:Name="ToggleVehicle01" -->
<!-- Background="{Binding BackColorSelectedVehicle01}" -->
<!-- IsChecked="{Binding SelectedVehicle01}" -->
<!-- ToolTip="{Binding Vehicle01Name}"> -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel (MainViewModel.cs):
using Caliburn.Micro;
using System;
using System.Collections.ObjectModel;
using System.IO;
namespace CMWpf02.ViewModels
{
public class MainViewModel : Screen, IHaveDisplayName
{
private String _path2Images = #"D:\tmp\Images";
public string DisplayName { get; set; }
public ObservableCollection<VehicleImage> VehicleImages { get; set; }
public MainViewModel()
{
DisplayName = "Main Window";
var vehicles = new ObservableCollection<String>();
vehicles = GetAllFilesFromFolder(_path2Images);
VehicleImages = new ObservableCollection<VehicleImage>();
foreach (var i in vehicles)
VehicleImages.Add(new VehicleImage(i));
}
public ObservableCollection<String> GetAllFilesFromFolder(String fullPathFolder)
{
string[] fileArray = Directory.GetFiles(fullPathFolder);
return new ObservableCollection<String>(fileArray);
}
}
public class VehicleImage
{
public String Image { get; private set; }
public String Name { get; private set; }
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
//public void ToggleVehicle01()
//{
// var selectText = (SelectedVehicle01) ? " selected" : " unselected";
// MessageBox.Show(Vehicle01Name + selectText);
// BackColorSelectedVehicle01 = (SelectedVehicle01) ? _backColorSelectedVehicle : _defaultBackColorVehicle;
//}
//public Boolean SelectedVehicle02
//{
// get { return _selectedVehicle02; }
// set
// {
// _selectedVehicle02 = value;
// NotifyOfPropertyChange(() => SelectedVehicle02);
// }
//}
//public Brush BackColorSelectedVehicle02
//{
// get { return _backColorSelectedVehicle02; }
// set
// {
// _backColorSelectedVehicle02 = value;
// NotifyOfPropertyChange(() => BackColorSelectedVehicle02);
// }
//public String Vehicle01Name { get; private set; }
}
EDIT: Now I can bind the properties of generated ToggleButton with view model. I make the VehicleImage class to a view model (see modified code below). But I still have problem to bind Click-event of generated ToggleButton to view model.
The modified class to view model
public class VehicleImage : PropertyChangedBase
{
public String Image { get; private set; }
public String Name { get; private set; }
private Boolean _selectedVehicle;
public Boolean SelectedVehicle
{
get { return _selectedVehicle; }
set
{
_selectedVehicle = value;
BackColorSelectedVehicle = _selectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
}
private Brush _backColorSelectedVehicle;
public Brush BackColorSelectedVehicle
{
get { return _backColorSelectedVehicle; }
set
{
_backColorSelectedVehicle = value;
NotifyOfPropertyChange(() => BackColorSelectedVehicle);
}
}
// ToggleButton's Click-Event Handler, but it doesn't get event trigger from View.
// Therefore I set the BackColorSelectedVehicle fin setter of SelectedVehicle property.
public void ToggleSelection()
{
//BackColorSelectedVehicle = SelectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
The modified view
<ToggleButton Width="180"
Margin="10,10,10,10"
Background="{Binding Path=BackColorSelectedVehicle}"
FontSize="10"
IsChecked="{Binding Path=SelectedVehicle}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
ToolTip="{Binding Path=Name}">
<!-- x:Name="ToggleSelection" -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>

WPF MVVM moving a UserControl from one ObservableCollection to another by event

I have a checklist view that has 2 ScrollViewers. One checklist is for incomplete items, the other is for complete items. They are populated by 2 separate observable collections and bound to by ItemsControls.
The UserControl has a button, when clicked will move that 'check' to the other collection.
Currently the way I have this setup is in the ViewModel that's the DataContext for the UserControl there is a public event that is subscribed to by the main window's VM by using:
((CheckItemVM) ((CheckListItem) cli).DataContext).CompleteChanged += OnCompleteChanged;
where cli is the checklist item.
then the OnCompleteChanged finds the appropriate View object by using:
foreach (object aCheck in Checks)
{
if (aCheck.GetType() != typeof (CheckListItem)) continue;
if (((CheckListItem) aCheck).DataContext == (CheckItemVM) sender)
{
cliToMove = (CheckListItem) aCheck;
break;
}
}
It's pretty obvious this breaks MVVM and I'm looking for a way around it (CheckListItem is the View, and CheckItemVM is it's DataContext ViewModel). Reasoning for the boxed type is I've got another UserControl that will have instances inside both, which are basically section labels, and I need to be able to sort my observable collections where there is an association between the checklistitem to a specific section by name.
This can be done in MVVM using commands, and bindings....
The idea that I propouse here is to create a command in the Windows view model, that manage the check command, and this command to receive the item view model in the params, then manage the the things in the command. I'm going to show you a simple example, using MvvmLight library:
The model:
public class ItemViewModel : ViewModelBase
{
#region Name
public const string NamePropertyName = "Name";
private string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
RaisePropertyChanging(NamePropertyName);
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion
#region IsChecked
public const string IsCheckedPropertyName = "IsChecked";
private bool _myIsChecked = false;
public bool IsChecked
{
get
{
return _myIsChecked;
}
set
{
if (_myIsChecked == value)
{
return;
}
RaisePropertyChanging(IsCheckedPropertyName);
_myIsChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
#endregion
}
A simple model with two property, one for the name (an identifier) and another for the check status.
Now in the Main View Model, (or Windows view model like you want)....
First the Collections, one for the checked items, and another for the unchecked items:
#region UncheckedItems
private ObservableCollection<ItemViewModel> _UncheckedItems;
public ObservableCollection<ItemViewModel> UncheckedItems
{
get { return _UncheckedItems ?? (_UncheckedItems = GetAllUncheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllUncheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(1,10))
{
toRet.Add(new ItemViewModel {Name = string.Format("Name-{0}", i), IsChecked = false});
}
return toRet;
}
#endregion
#region CheckedItems
private ObservableCollection<ItemViewModel> _CheckedItems;
public ObservableCollection<ItemViewModel> CheckedItems
{
get { return _CheckedItems ?? (_CheckedItems = GetAllCheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllCheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(11, 20))
{
toRet.Add(new ItemViewModel { Name = string.Format("Name-{0}", i), IsChecked = true });
}
return toRet;
}
#endregion
And the command:
#region CheckItem
private RelayCommand<ItemViewModel> _CheckItemCommand;
public RelayCommand<ItemViewModel> CheckItemCommand
{
get { return _CheckItemCommand ?? (_CheckItemCommand = new RelayCommand<ItemViewModel>(ExecuteCheckItemCommand, CanExecuteCheckItemCommand)); }
}
private void ExecuteCheckItemCommand(ItemViewModel item)
{
//ComandCode
item.IsChecked = true;
UncheckedItems.Remove(item);
CheckedItems.Add(item);
}
private bool CanExecuteCheckItemCommand(ItemViewModel item)
{
return true;
}
#endregion
The magic here could be in the Data binding, in this case I used command parameter and the FindAncestor binding, check the Data Template:
<DataTemplate x:Key="UncheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
<Button Content="Check" Width="75" Command="{Binding DataContext.CheckItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Mode=OneWay}"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="CheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
</StackPanel>
</Grid>
</DataTemplate>
One data template for checked items, and another for unchecked items. Now the usage, this is simpler:
<ListBox Grid.Row="2" Margin="5" ItemsSource="{Binding UncheckedItems}" ItemTemplate="{DynamicResource UncheckedItemDataTemplate}"/>
<ListBox Grid.Row="2" Margin="5" Grid.Column="1" ItemsSource="{Binding CheckedItems}" ItemTemplate="{DynamicResource CheckedItemDataTemplate}"/>
This is a cleaner solution, hope is helps.

DataTemplate is not generating ListItemBox

I want to generate ListItemBox using DataTemplate but items are not generating. Please guide me where is the mistake. I have following code in MainWindow.xaml.
<Window x:Class="Offline_Website_Downloader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bd="clr-namespace:Offline_Website_Downloader"
Title="Offline Website Downloader" Background="#f5f6f7" Height="500" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<bd:BindingController x:Key="BindingControllerKey" />
<DataTemplate x:Key="DownloadedWebsitesListBox">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock FontWeight="Bold" FontSize="18" Width="480">
<Hyperlink NavigateUri="http://google.com">
<Label Content="{Binding Path=WebsiteTitle}" />
</Hyperlink>
</TextBlock>
<TextBlock Width="132" TextAlignment="right">
<TextBlock Text="Remaining Time: "/>
<TextBlock Name="TimeRemaining" Text="js"/>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ProgressBar Name="progress1" Maximum="100" Minimum="0" Value="30" Background="#FFF" Width="612" Height="10" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Width="450">Status: <TextBlock Text="{Binding Path=Status}"/></TextBlock>
<TextBlock Width="162" TextAlignment="right">
<TextBlock Text="Downloading Speed: "/>
<TextBlock Name="DownloadingSpeed" Text="{Binding Path=DownloadingSpeed}"/>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
</Grid>
</window>
and MainWindow.xaml.cs
BindingController bc = new BindingController();
public MainWindow()
{
InitializeComponent();
bc.DownloadingSpeed = "40kb/s";
bc.WebsiteTitle = "WebsiteTitle";
bc.Status = "Downloading";
DataContext = bc;
}
and BindingController.cs
public class BindingController
{
public BindingController()
{
}
private string _WebsiteTitle;
public string WebsiteTitle
{
set { _WebsiteTitle = value; }
get { return _WebsiteTitle; }
}
private string _Status;
public string Status
{
set { _Status = value ; }
get { return _Status ; }
}
private string _DownloadStartDate;
public string DownloadStartDate
{
set { _DownloadStartDate = value; }
get { return _DownloadStartDate ; }
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
set { _DownloadingSpeed = value; }
get { return _DownloadingSpeed; }
}
}
Your problem is that you're binding a ListBox to an object that contains several properties, but really only represents a single object/state. The ListBox expects to display a list of items (i.e. IList, IBindingList, IEnumerable, ObservableCollection).
Assuming you want to display more than one download at a time, which I'm assuming given that you're using a ListBox, I would refactor the download properties into a separate class. You will also need to implement INotifyPropertyChanged on your properties so that when the values are changed, they will be shown in the UI.
public class Download : INotifyPropertyChanged
{
private string _WebsiteTitle;
public string WebsiteTitle
{
get { return _WebsiteTitle; }
set
{
if (_WebsiteTitle == value)
return;
_WebsiteTitle = value;
this.OnPropertyChanged("WebsiteTitle");
}
}
private string _Status;
public string Status
{
get { return _Status; }
set
{
if (_Status == value)
return;
_Status = value;
this.OnPropertyChanged("Status");
}
}
private string _DownloadStartDate;
public string DownloadStartDate
{
get { return _DownloadStartDate; }
set
{
if (_DownloadStartDate == value)
return;
_DownloadStartDate = value;
this.OnPropertyChanged("DownloadStartDate");
}
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
get { return _DownloadingSpeed; }
set
{
if (_DownloadingSpeed == value)
return;
_DownloadingSpeed = value;
this.OnPropertyChanged("DownloadingSpeed");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The new BindingController:
public class BindingController
{
public BindingController()
{
this.Downloads = new ObservableCollection<Download>();
}
public ObservableCollection<Download> Downloads { get; private set; }
}
Setting up the bindings in XAML:
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding Downloads}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
Initializing the collection in MainWindow
Download download = new Download();
download.DownloadingSpeed = "40kb/s";
download.WebsiteTitle = "WebsiteTitle";
download.Status = "Downloading";
bc.Downloads.Add(download);
this.DataContext = bc;

Categories