Change DataTemplate for ListBox item if selected in UWP - c#

In UWP Xaml,I have a ListView. In the ListView i use a DataTemplate with a stack panel,
I want change backgrund color of stackpanel when selectedItem is True
I do want to do it in Xaml
the other words,
Change DataTemplate for Listview item if selected
I do want to do it in Xaml
code in Xaml:
<ListView.ItemTemplate >
<DataTemplate x:Name="mydt">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="MyStack">
<ContentControl Content="{Binding rtb}"
q:APGolAyah.MyProperty="{Binding AyahNo}" />
<TextBlock Text="{Binding Text}"
TextWrapping="Wrap" />
</StackPanel>
<Image Grid.Column="1"
Source="{Binding HezbNo,Converter={StaticResource HezbNoToIconConverter}}"
Width="25" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

As you've found that in UWP Style.Triggers cannot be used, and I understand that you want to do this work pure in xaml, but sorry to say that there is no such pure way now.
Since you only want to change the background color of the StackPanel when the item is selected, I think it is not needed to changed the whole DataTemplate, I'm writing the answer here to introduce a method which uses Converter for data binding and a little bit c# code here since maybe you interest in this method.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<local:BoolToBrushConverter x:Key="cvt" />
</Grid.Resources>
<ListView x:Name="listview" ItemsSource="{x:Bind Collection, Mode=OneWay}"
SelectionChanged="listview_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackPanel Background="{Binding IsSelected, Converter={StaticResource cvt}}">
<TextBlock Text="{Binding Name}" />
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code behind and data model:
private ObservableCollection<Model> Collection = new ObservableCollection<Model>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
for (int i = 0; i < 50; i++)
{
Collection.Add(new Model { Name = "Name " + i + ", ", Age = i });
}
}
private void listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (Model item in e.AddedItems)
{
item.IsSelected = true;
}
foreach (Model item in e.RemovedItems)
{
item.IsSelected = false;
}
}
public class Model : INotifyPropertyChanged
{
public string Name { get; set; }
public int Age { get; set; }
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (value != _IsSelected)
{
_IsSelected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Converter:
public class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool? b = (bool?)value;
if (b == true)
return new SolidColorBrush(Colors.BlueViolet);
return new SolidColorBrush(Colors.Transparent);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And if you also interest in changing DataTemplate, you may refer to my answer in another case: UWP ListView: How to expand an item when select it?.

Related

Dynamic ViewModel's bound values becomes null in WPF

I followed few online tutorials to switch between dynamic views from a ListView.
In my MainWindow, I have a ListView in the left pane with ItemsSource of list of sub ViewModels. (Each sub ViewModel implements an Interface)
Each ViewModel have its own View as a DataTemplate.
I'm calling the GenerateReport() method of the selected view from the MainWindow. But the values of selected views becomes null.
Download my source from Github.
Steps to reproduce the issue:
Run the application and type text in the Students Report's Id and Name. (The breakpoints in StudentReportViewModels's properties are properly hitting and values updated.)
Then click Generate Report button. The StudentReportViewModels's properties values becomes null.
How to fix the issue? Please help.
Source:
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:StudentsReportViewModel}">
<view:StudentsReport/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MarksReportViewModel}">
<view:MarksReport />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Reports}" SelectedItem="{Binding SelectedReport, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" HorizontalAlignment="Stretch"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Content="{Binding SelectedReport.ViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
<Button Content="Generate Report" Grid.Row="2" Margin="5" HorizontalAlignment="Right" Command="{Binding GenerateReportCommand}"/>
</Grid>
</Grid>
MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ICommand _generateReportCommand;
private Report selectedReport;
public Report SelectedReport
{
get
{
return this.selectedReport;
}
set
{
if (value != this.selectedReport)
{
this.selectedReport = value;
NotifyPropertyChanged();
}
}
}
public List<Report> Reports { get; set; }
public ICommand GenerateReportCommand
{
get
{
if (_generateReportCommand == null)
{
_generateReportCommand = new RelayCommand(
p => GenerateReport()
);
}
return _generateReportCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowViewModel()
{
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
SelectedReport = Reports[0];
}
public void GenerateReport()
{
SelectedReport.ViewModel.GenerateReport();
}
}
StudentsReport.xaml
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
StudentsReportViewModel:
public class StudentsReportViewModel : INotifyPropertyChanged, IReportViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string id;
public string Id
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged();
}
}
}
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged();
}
}
}
public StudentsReportViewModel()
{
}
public void GenerateReport()
{
System.Windows.MessageBox.Show($"Id = {Id}, Name = {Name}");
}
}
Interface:
public interface IReportViewModel
{
void GenerateReport();
}
Model:
public class Report
{
public string Name { get; set; }
public IReportViewModel ViewModel { get; set; }
}
Your StudentsReport.xaml UserControl is binding to an instance of the StudentsReportViewModel created in the XAML:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
The Generate Report button however is calling into another instance of the StudentsReportViewModel that is create in the MainWindowVieModel constructor and is stored in the Report class.
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
You need to remove one of these instances so that the UserControl's DataContext is bound to the same view model instance that you are generating the report message from.
I suggest deleting this code from StudentsReport.xaml:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>

WPF C# datagrid combobox on new row won't update datasource (updated!)

This is an example exhibiting the behaviour I'm having trouble with. I have a datagrid which is bound to an observable collection of records in a viewmodel. In the datagrid I have a DataGridTemplateColumn holding a combobox which is populated from a list in the viewmodel. The datagrid also contains text columns. There are some textboxes at the bottom of the window to show the record contents.
<Window x:Class="Customer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Customer"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:SelectedRowConverter x:Key="selectedRowConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<DataGrid x:Name="dgCustomers" AutoGenerateColumns="False"
ItemsSource="{Binding customers}" SelectedItem="{Binding SelectedRow,
Converter={StaticResource selectedRowConverter}, Mode=TwoWay}"
CanUserAddRows="True" Grid.Row="0" SelectionChanged="dgCustomers_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbCountry" ItemsSource="{Binding DataContext.countries,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="name" SelectedValuePath="name" Margin="5"
SelectedItem="{Binding DataContext.SelectedCountry,
RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" SelectionChanged="cmbCountry_SelectionChanged" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding name}" Width="1*"/>
<DataGridTextColumn Header="Phone" Binding="{Binding phone}" Width="1*"/>
</DataGrid.Columns>
</DataGrid>
<Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<BulletDecorator Grid.Column="0">
<BulletDecorator.Bullet>
<Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="1">
<BulletDecorator.Bullet>
<Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="2">
<BulletDecorator.Bullet>
<Label Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
</BulletDecorator>
</Grid>
</Grid>
</Window>
Initially there are no records so the datagrid is empty and shows just one line containing the combobox. If the user enters data into the text columns first then a record is added to the collection and the combobox value can be added to the record. However, if the user selects the combobox value first, then the value disappears when another column is selected. How do I get the combobox data added to the record if it is selected first?
Codebehind:
public partial class MainWindow : Window
{
public GridModel gridModel { get; set; }
public MainWindow()
{
InitializeComponent();
gridModel = new GridModel();
//dgCustomers.DataContext = gridModel;
this.DataContext = gridModel;
}
private void cmbCountry_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox c = sender as ComboBox;
Debug.Print("ComboBox selection changed, index is " + c.SelectedIndex + ", selected item is " + c.SelectedItem);
}
}
The Record class:
public class Record : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private string _phone;
public string phone
{
get { return _phone; }
set
{
_phone = value;
OnPropertyChanged("phone");
}
}
private int _countryCode;
public int countryCode
{
get { return _countryCode; }
set
{
_countryCode = value;
OnPropertyChanged("countryCode");
}
}
}
Country class:
public class Country : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("id");
}
}
private int _code;
public int code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged("code");
}
}
public override string ToString()
{
return _name;
}
}
GridModel:
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> customers { get; set; }
public List<Country> countries { get; set; }
public GridModel()
{
customers = new ObservableCollection<Record>();
countries = new List<Country> { new Country { id = 1, name = "England", code = 44 }, new Country { id = 2, name = "Germany", code = 49 },
new Country { id = 3, name = "US", code = 1}, new Country { id = 4, name = "Canada", code = 11 }};
}
private Country _selectedCountry;
public Country SelectedCountry
{
get
{
return _selectedCountry;
}
set
{
_selectedCountry = value;
_selectedRow.countryCode = _selectedCountry.code;
OnPropertyChanged("SelectedRow");
}
}
private Record _selectedRow;
public Record SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
Debug.Print("Datagrid selection changed");
OnPropertyChanged("SelectedRow");
}
}
}
Converters:
class Converters
{
}
public class SelectedRowConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Record)
return value;
return new Customer.Record();
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
The behaviour you are seeing is as expected. The reason behind it is that the ComboBox ItemsSource as well as SelectedItem both are bound to Properties of the Window's DataContext while the other columns are bound to your DataGrid's ItemsSource. Hence when you modify the columns other than the dropdown the data is added to the observable collection.
What you can do is after a value is selected from the drop down you need to add a record yourself (possibly by calling a function from your SelectedCountry property)
EDIT
Based on your code I made a working model making as little changes as possible to your existing code. I could not use the converter as I did not have the details of the class Customer
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Button HorizontalAlignment="Right" Content="Add User" Margin="0,2,2,2" Command="{Binding AddUserCommand}"/>
<DataGrid x:Name="dgCustomers"
AutoGenerateColumns="False"
ItemsSource="{Binding customers}"
SelectedItem="{Binding SelectedRow}"
SelectionUnit="FullRow"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Focusable="False"
ItemsSource="{Binding DataContext.countries, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="name"
SelectedValuePath="code"
SelectedValue="{Binding countryCode, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding name, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
<DataGridTextColumn Header="Phone" Binding="{Binding phone, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<BulletDecorator Grid.Column="0">
<BulletDecorator.Bullet>
<Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="1">
<BulletDecorator.Bullet>
<Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="2">
<BulletDecorator.Bullet>
<Label Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
</BulletDecorator>
</Grid>
</Grid>
Your GridModel class
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> customers { get; set; }
public ObservableCollection<Country> countries
{
get;
private set;
}
public GridModel()
{
customers = new ObservableCollection<Record> { };
AddUserCommand = new RelayCommand(AddNewUser);
countries = new ObservableCollection<Country>
{
new Country { id = 1, name = "England", code = 44 },
new Country { id = 2, name = "Germany", code = 49 },
new Country { id = 3, name = "US", code = 1},
new Country { id = 4, name = "Canada", code = 11 }
};
}
private void AddNewUser()
{
customers.Add(new Record());
}
public ICommand AddUserCommand { get; set; }
private Record _selectedRow;
public Record SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
Debug.Print("Datagrid selection changed");
OnPropertyChanged("SelectedRow");
}
}
}
I have used MVVMLight toolkit which contains RelayCommand. You can also define your own ICommand implementation and use it instead of the toolkit
EDIT 2
Fixed the bug introduced by me which would prevent the combobox from displaying the Country if the data comes from the data base. The improved code does not require any converter either

Define command binding in user control

I wrote user control with 2 buttons and one check box and now I want to bind Commands to data context - for each button and checkbox.
But I don't know how to define command binding. I think I'll need some kind of ICommand property in User control - but how can I connect user's data context command delegate? I want to use user control to manage each item in collection like this:
<ItemsControl ItemsSource="{Binding Path=MoneyInfo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelSetupControl
CurrentCount="{Binding Count}"
CoinValue="{Binding Value}"
UpCommand="{Binding DataContextUp}"
DownCommand="{Binding DataContextDown}"
ChangeCheckboxCommand="{Binding DataContextChange}"></local:ChannelSetupControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
XAML User control
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="{Binding CoinValue}" TextAlignment="Center"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding CurrentCount, Mode=TwoWay}" TextAlignment="Center" VerticalAlignment="Center" FontSize="30"></TextBlock>
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Center">
<Button Content="+ 10" Padding="0 5"></Button>
<Button Content="- 10" Padding="0 5"></Button>
</StackPanel>
<CheckBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" IsChecked="{Binding Cycling, Mode=TwoWay}" Content="recycling" VerticalContentAlignment="Center"></CheckBox>
</Grid>
</UserControl>
and code behind and this is where I'm lost - how to define UpCommand, DownCommand and ChangeCheckboxCommand?
public partial class ChannelSetupControl : UserControl, INotifyPropertyChanged
{
private int currentCount;
private bool cycling;
private double coinValue;
public int Step { get; set; }
public double CoinValue { get { return coinValue; } set { coinValue = value; NotifyPropertyChanged("CoinValue"); } }
public int CurrentCount { get { return currentCount; } set { currentCount = value; NotifyPropertyChanged("CurrentCount"); } }
public bool Cycling { get { return cycling; } set { cycling = value; NotifyPropertyChanged("Cycling"); } }
public ChannelSetupControl()
{
InitializeComponent();
DataContext = this;
CurrentCount = 0;
Step = 10;
Cycling = false;
CoinValue = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First of all your ChannelSetupControl class extends UserControl, so it implicitly extends DependencyObject class. It means you can use Dependency Properties instead of implementing INotifyPropertyChanged.
So you can define a dependency property in your ChannelSetupControl class, like this one:
public static readonly DependencyProperty UpCommandProperty =
DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(ChannelSetupControl));
public ICommand UpCommand
{
get { return (ICommand)GetValue(UpCommandProperty); }
set { SetValue(UpCommandProperty, value); }
}
At the same time in your control XAML:
<Button Command="{Binding RelativeSource={RelativeSource Mode=Self}, Path=UpCommand, Mode=OneWay}"
Content="+ 10" Padding="0 5" />
In this way in your window XAML you can wrote:
<local:ChannelSetupControl UpCommand="{Binding UpCommand, Mode=OneWay}" ... />
You can use the same "pattern" for the other controls.
Regarding ICommand, there are a lot of implementations. The one that I prefer is the so called delegate command (for a sample you can take a look here).
I hope this quick explanation can help you.

Nested collection LLS ItemsSource binding with Grouping

I'm trying to bind an ObservableCollection of custom objects called ItemGroup which itself contains an ObservableCollection of Item objects (among other Properties) to a LongListSelector (to create a grouping of Item objects) in WP8.1 Silverlight:
public class Item : INotifyPropertyChanged {
private string _name = "";
public string Name {
get {
return _name;
}
set {
if (value != _name) {
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private int _cost = 0;
public int Cost {
get {
return _cost;
}
set {
if (value != _cost) {
_cost = value;
NotifyPropertyChanged("Cost");
}
}
}
}
public class ItemGroup : INotifyPropertyChanged {
private string _groupName = "";
public string GroupName {
get {
return _groupName;
}
set {
if (value != _groupName) {
_groupName = value;
NotifyPropertyChanged("GroupName");
}
}
}
private int _totalCost = 0;
public int TotalCost {
get {
return _totalCost;
}
set {
if (value != _totalCost) {
_totalCost = value;
NotifyPropertyChanged("TotalCost");
}
}
}
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items {
get {
return _items;
}
set {
if (value != _items) {
_items = value;
NotifyPropertyChanged("Items");
}
}
}
}
ViewModel contains the property:
private ObservableCollection<ItemGroup> _itemGroupList = new ObservableCollection<ItemGroup>();
public ObservableCollection<ItemGroup> ItemGroupList {
get {
return _itemGroupList;
}
set {
if (value != _itemGroupList) {
_itemGroupList = value;
NotifyPropertyChanged("ItemGroupList");
}
}
}
And the XAML:
<phone:LongListSelector ItemsSource="{Binding ItemGroupList}">
<!-- Template for ItemGroup Items -->
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Height="Auto" Margin="12,0,2,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Item Name -->
<TextBlock Text="{Binding Items.Name}" Grid.Column="0"/>
<!-- Item Cost -->
<TextBlock Text="{Binding Items.Cost, StringFormat='{}{0} dollars'}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
<!-- Template for ItemGroup headers -->
<phone:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Grid Background="#FFA2A2A2" Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ItemGroup Group Name -->
<TextBlock Grid.Column="0" Text="{Binding GroupName}"/>
<!-- ItemGroup Total Cost -->
<TextBlock Grid.Column="1" Text="{Binding TotalCost}"/>
</Grid>
</DataTemplate>
<phone:LongListSelector.GroupHeaderTemplate>
</phone:LongListSelector>
Which isn't working. The direct Properties of ItemGroup (GroupName and TotalCost) bind correctly and are visible, but no Item objects are shown! I suspect this is because I am passing to ItemTemplate the wrong source? I have tried binding the Collection directly in the LLS by ItemSource={Binding ItemGroupList.Items}, but that too doesn't work.
Update: Binding a Header control to Items.Count yields the actual number of Item objects inside Items of ItemGroup, which indicates that there indeed exists a list of Item objects for each ItemGroup.
Note that you've got a nested collection there, so you will need 2 items controls. You can't bind to "Items.Name" or "Items.Cost" because those are not properties. You could use "Items[0].Name", but I guess that you would probably want to list all the items, something like this (inside "LongListSelector.ItemTemplate"):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Item Name -->
<TextBlock Text="{Binding Items.Name}" Grid.Column="0"/>
<!-- Item Cost -->
<TextBlock Text="{Binding Items.Cost, StringFormat='{}{0} dollars'}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Failing To Bind Dynamically Generated Buttons & Textboxes in WPF

I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.
Xaml:
<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" Margin="50,20,0,0"></Label>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
<Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Model Class:
private string _ChanelName = "";
public String ChanelName
{
get
{
return _ChanelName;
}
set
{
if (value != _ChanelName)
{
_ChanelName = value;
OnPropertyChanged("ChanelName");
}
}
}
// Constructor
public VoltageModel(string ChanelName)
{
this.ChanelName = ChanelName;
}
public override string ToString()
{
return _ChanelName;
}
ViewModel Class:
class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
private string _VoltageText;
public string VoltageText
{
get { return _VoltageText; }
set
{
_VoltageText = value;
OnPropertyChanged("VoltageText");
}
}
// Method gets called when Set Button Is Clicked
public void SetCommandExecuted()
{
string val = VoltageText;
}
//Notify Property Changed members are present
}
Xaml.cs Class:
ChannelList myChanels = new ChannelList();
public VoltageView() // Constructor
{
InitializeComponent();
myChanels.Add(new VoltageModel("VDD__Main"));
myChanels.Add(new VoltageModel("VDD__IO__AUD"));
myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));
myViewChannelList.DataContext = myChanels;
}
This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.
Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???
Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.
Here is the view
.xaml
<StackPanel>
<ListBox HorizontalAlignment="Stretch"
Height="Auto"
ItemsSource="{Binding VoltageCollection}"
VerticalAlignment="Stretch"
Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="100"
Content="{Binding ChannelName}" />
<TextBox Width="100"
Text="{Binding VoltageText}" />
<Button Margin="10,0,0,0"
Content="Set"
Command="{Binding VoltageCommand}"
CommandParameter="{Binding VoltageText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the code behind
.xaml.cs
private ChannelListViewModel m_voltageViewModel;
public MainWindow()
{
InitializeComponent();
m_voltageViewModel = new ChannelListViewModel();
m_voltageViewModel.Initialize();
DataContext = m_voltageViewModel;
}
Here is the Model: VoltageModel
public class VoltageModel : INotifyPropertyChanged
{
public string ChannelName { get; set; }
private string m_voltageText;
public string VoltageText
{
get { return m_voltageText; }
set
{
m_voltageText = value;
OnPropertyChanged("VoltageText");
}
}
public ICommand VoltageCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the ViewModel: ChannelListViewModel
public class ChannelListViewModel
{
private ICommand m_voltageCommand;
public ChannelListViewModel()
{
m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
}
public void Initialize()
{
VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__CODEC__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }};
}
public ObservableCollection<VoltageModel> VoltageCollection { get; set; }
public void SetCommandExecute(object voltageText)
{
Debug.WriteLine(voltageText);
}
}
Finally simple DelegateCommand class DelegateCommand
public class DelegateCommand : ICommand
{
Action<object> m_executeDelegate;
public DelegateCommand(Action<object> executeDelegate)
{
m_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
m_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .
the first : your DataTemplate places your control one on top of the other .
fix :
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
<TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
<Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
</Grid>
</DataTemplate>
the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.
fix :
private T _property;
public T Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
make these fixes and edit your post if you still have issues post a comment under my answer.

Categories