WPF Bind UserControl Property inside of the DataTemplate of a GridViewColumn.CellTemplate - wpf

I'm trying to figure out how to bind a property of a custom user control that is placed inside of the cell template of a list view control but it's not working. All of the DisplayMemberBinding fields are working as expected, and I'm getting the correct values, but inside of that custom control, nothing is updating.
WPF LIstView Control
<ListView Margin="10" x:Name="lvHistory">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Database" Width="150" DisplayMemberBinding="{Binding ActiveBackup.Database.Name, Mode=TwoWay}" />
<GridViewColumn Header="Start Time" Width="130" DisplayMemberBinding="{Binding ActiveBackup.StartTime, Mode=TwoWay}" />
<GridViewColumn Header="Time Elapsed" Width="100" DisplayMemberBinding="{Binding ActiveBackup.TimeElapsed, Mode=TwoWay}" />
<GridViewColumn Header="P2" Width="100" DisplayMemberBinding="{Binding Progress, Mode=TwoWay}" />
<GridViewColumn x:Name="progressColumn" Header="Progress" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:cProgressBarSmall x:Name="pr1" Value="{Binding Progress, Mode=TwoWay}" Visibility="Visible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code-Behind in the cProgressBarSmall control.
public partial class cProgressBarSmall : UserControl
{
public ActiveBackup ActiveBackup { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall));
private decimal _value;
public decimal Value
{
get
{
return (decimal) GetValue(ValueProperty);
}
set
{
_value = value;
SetValue(ValueProperty, value);
p1.Value = value.ToDoubleNotNull();
pLabel.Text = value.ToPercent(0);
if (value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}
}
}
}
I can't find a way to access the "pr1" because it's in a DataTemplate and therefore not directly accessible from the code-behind. Does binding not work through? The column before it (the "P2" column) is just at test column I put in just to make sure that the value is in fact updating and it is and that displays correctly, however the "progressColumn" always just shows the default value.
Is there anything special to data binding inside of a ListView.View > GridView > GridViewColumn > GridViewColumn.CellTemplate > DataTemplate hierarchy?

First, if you put a breakpoint in your setter you'll find that it's not hit by the binding. That's because the Binding is setting the dependency property, not the C# property. They're different. The C# property with get/set is an optional wrapper around the dependency property.
The correct way to do this is to have little or no code behind (code behind's not evil; you just don't need any for this one), but use a binding in the usercontrol xaml to update the UI. You can hide and show controls, and update label text, with style triggers in the usercontrol XAML. You don't need any code behind for this.
But here's the simplest way to adapt your existing code to something that works.
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall),
new PropertyMetadata(0m, Value_ChangedCallback));
// Has to be static
private static void Value_ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((cProgressBarSmall)d).OnValueChanged();
}
private void OnValueChanged()
{
p1.Value = Value.ToDoubleNotNull();
pLabel.Text = Value.ToPercent(0);
if (Value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (Value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}

Related

WPF PropertyChangedEvent is always null [duplicate]

I did my research that people tend to use ViewModel to achieve this but I am sort of stuck in it.
I have a
public ObservableCollection<Order> orderList { get; set; } = new ObservableCollection<Order>();
in MainWindow which is already filled up with data.
in MainWindow XAML I have a User Control inside the TabControl:
<TabControl x:Name="TabCollection">
<TabItem Header="UC1">
<local:UserControl1/>
</TabItem>
<TabItem Header="UC2">
<local:UserControl2/>
</TabItem>
</TabControl>
We only talk about UC1 here so in UC1 XAML here I have a ListView inside:
<UserControl.DataContext>
<local:UserControl1VM/>
</UserControl.DataContext>
<ListView x:Name="ListViewText">
<ListView.View>
<GridView>
<GridViewColumn Header="First name" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Lastname}"/>
<GridViewColumn Header="Order" DisplayMemberBinding="{Binding Ordername}"/>
<GridViewColumn Header="Delivery time" DisplayMemberBinding="{Binding Deliverytime}"/>
<GridViewColumn Header="Phone Number" DisplayMemberBinding="{Binding Phone}"/>
<GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}"/>
<GridViewColumn Header="Email" DisplayMemberBinding="{Binding Email}"/>
</GridView>
</ListView.View>
</ListView>
And here's the code in UserControl1VM.cs:
namespace QuickShop
{
class UserControl1VM : INotifyPropertyChanged
{
private ObservableCollection<Order> orderList;
public ObservableCollection<Order> OrderList
{
get { return orderList; }
set
{
orderList = value;
PropertyChanged(this, new PropertyChangedEventArgs("OrderList"));
}
}
//
private void FindDeliveryOrders(IEnumerable<Order> sortList)
{
foreach (var order in sortList)
{
if (order.Delivery.Equals("Yes"))
{
//deliveryOrders.Add(order);
this.ListViewText.Items.Add(new Order { Firstname = order.Firstname, Lastname = order.Lastname, Ordername = order.Ordername, Deliverytime = order.Deliverytime, Phone = order.Phone, Address = order.Address, Email = order.Email });
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
And Of course these are incomplete codes because I don't know how to proceed next.
My goal is just to populate the ListView and it will automatically update itself if orderList changes. But right now I couldn't even know whether the ViewModel is working or not, any thoughts and code demo would be very grateful.
A UserControl should never have a "private" view model, as you assign it to the DataContext in the UserControl's XAML. It should instead expose dependency properties that could be bound to properties of an externally provided view model object.
Declare an ItemsSource property like this:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource), typeof(IEnumerable), typeof(UserControl1));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public UserControl1()
{
InitializeComponent();
}
}
And bind the ListView like this:
<UserControl ...>
...
<ListView ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}">
...
</ListView>
...
</UserControl>
When you use the UserControl, bind the property to a view model property:
<TabItem Header="UC1">
<local:UserControl1 ItemsSource="{Binding OrderList}"/>
</TabItem>
The last XAML snippet assumes that the object in the UserControl's DataContext has a OrderList property. This would automatically happen when the TabControl is bound to a collection of view model objects with that property.
Alternatively, let the elements in the UserControl's XAML directly bind to the properties of the object in the inherited DataContext.
<UserControl ...>
...
<ListView ItemsSource="{Binding OrderList}">
...
</ListView>
...
</UserControl>
Your control would not have to expose additional bindable properties, but it would only work with DataContext objects that actually provide the expected source properties.

Selected item in a listview doesn't unselect

I can select multiple items in a listview. But if i click on one, it turns blue. That's normal, so that shows it is selected. But if i click again on the same item, it doesnt uncheck. So i can't change my selection. Somebody who knows how to fix this stupid little problem?
Edit: This is my listview:
<ListView Height="155" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectedItem="{Binding Path=SelectedQuestionDropList, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectionMode="Multiple" Margin="0,0,542,436" Background="#CDC5CBC5"
dd:DragDrop.DropHandler="{Binding}" Name="DropListView" ItemsSource="{Binding Path=SelectedExaminationQuestions,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" SelectionChanged="ListView_SelectionChanged_1" VerticalAlignment="Bottom">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Verkorte naam" Width="Auto" DisplayMemberBinding="{Binding Path=ShortName}" />
<GridViewColumn Header="Omschrijving" Width="Auto" DisplayMemberBinding="{Binding Path=Description}" />
<GridViewColumn Header="Type" Width="Auto" DisplayMemberBinding="{Binding Path=Type}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
I was facing a similar problem and found that, while left-click always select the item pointed to, you can use Ctrl + left-click to toggle selection in a list view. This is the default behavior.
You can write a wpf behaviour. Something like:
public class ListViewBehaviour
{
/// <summary>
/// Enfoca automaticament el item sel·leccionat
/// </summary>
public static readonly DependencyProperty AutoUnselectItemProperty =
DependencyProperty.RegisterAttached(
"AutoUnselect",
typeof(bool),
typeof(ListViewBehaviour),
new UIPropertyMetadata(false, OnAutoUnselectItemChanged));
public static bool GetAutoUnselectItem(ListView listBox)
{
return (bool)listBox.GetValue(AutoUnselectItemProperty);
}
public static void SetAutoUnselectItem(ListView listBox, bool value)
{
listBox.SetValue(AutoUnselectItemProperty, value);
}
private static void OnAutoUnselectItemChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var listView = source as ListView;
if (listView == null)
return;
if (e.NewValue is bool == false)
listView.SelectionChanged -= OnSelectionChanged;
else
listView.SelectionChanged += OnSelectionChanged;
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// TODO write custom selection behaviour
}
}
And to apply it to a listview:
<ListView bb:ListViewBehaviour.AutoUnselect="True">
...
</ListView>

GridView DoubleClick

I have a GridView where I want to detect a doubleclick event on the items in the list, i do it as follows:
<ListView>
<ListView.View >
<GridView >
<GridViewColumn Header="FileName">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding FileName}" MouseDoubleClick="Configuration_MouseDoubleClick"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding CreationDate}" Header="Date"/>
</GridView>
</ListView.View>
</ListView>
The problem is that I can only detect doubleclicks by attaching it to the control in the template.
How can I attach the MouseDoubleClick event to the whole ListViewItem? Is there any solution for that with PRISM?
You can add the MouseDoubleClick event to ListViewItem in the ItemContainerStyle like this
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Code behind..
void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//...
}
If you're doing MVVM, you can bridge the gap between codebehind and your viewmodel the usual way--by using an attached behavior:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public sealed class HandleDoubleClickBehavior
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
"Command", typeof (ICommand), typeof (HandleDoubleClickBehavior), new PropertyMetadata(default(ICommand), OnComandChanged));
public static void SetCommand(DependencyObject element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject element)
{
return (ICommand) element.GetValue(CommandProperty);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
"CommandParameter", typeof (object), typeof (HandleDoubleClickBehavior), new PropertyMetadata(default(object)));
public static void SetCommandParameter(DependencyObject element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject element)
{
return (object) element.GetValue(CommandParameterProperty);
}
private static void OnComandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = d as Control;
if (c == null)
throw new InvalidOperationException($"can only be attached to {nameof(Control)}");
c.MouseDoubleClick -= OnDoubleClick;
if (GetCommand(c) != null)
c.MouseDoubleClick += OnDoubleClick;
}
private static void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
var d = sender as DependencyObject;
if (d == null)
return;
var command = GetCommand(d);
if (command == null)
return;
var parameter = GetCommandParameter(d);
if (!command.CanExecute(parameter))
return;
command.Execute(parameter);
}
}
With that in your toolbox, you can write XAML like this (assuming PersonViewModel contains the string properties Name and Title, and an ICommand property named SayHiCommand that expects a string parameter):
<ListView ItemsSource="{Binding Persons}" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="local:HandleDoubleClickBehavior.Command" Value="{Binding SayHiCommand}" />
<Setter Property="local:HandleDoubleClickBehavior.CommandParameter" Value="{Binding Name}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" />
</GridView>
</ListView.View>
</ListView>
For those here that are using a framework like Prism where the main usercontrol containing the Listview is a View (not a window) that is bound to a viewmodel.
The answer of dlf is the best with these small tweaks:
{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SayHiCommand}
And in the special behavior attached property you change the ICommand cast to a DelegateCommand cast.
Works like a charm,
Thank you very much.

ListView DataTemplate binding

I have the following ListView:
<ListView Name="listView">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility"
Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked}"
Visibility="{Binding IsChecked, Converter={StaticResource boolToVis}}">
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="0"
Text="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The items in the ListView are of the below type:
public class CheckBoxListViewItemSource : INotifyPropertyChanged
{
public CheckBoxListViewItemSource(String text)
{
m_text = text;
}
public bool IsChecked
{
get { return m_checked; }
set
{
if (m_checked == value) return;
m_checked = value;
RaisePropertyChanged("IsChecked");
}
}
public String Text
{
get { return m_text; }
set
{
if (m_text == value) return;
m_text = value;
RaisePropertyChanged("Text");
}
}
public override string ToString()
{
return Text;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
PropertyChangedEventHandler eh = PropertyChanged;
if (eh != null)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
private bool m_checked;
private String m_text;
}
The visibility of the checkbox in the ListView is bound to the value of IsChecked of the ListViewItem. The converter is a simple bool to visibility converter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
In the code behind of the ListView I have:
void listView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
CheckBoxListViewItemSource source = item as CheckBoxListViewItemSource;
source.IsChecked = false;
}
foreach (var item in e.AddedItems)
{
CheckBoxListViewItemSource source = item as CheckBoxListViewItemSource;
source.IsChecked = true;
}
}
The binding for the checkbox visibility is not working for me. The default IsChecked value is false so the list appears with no checkboxes. If I select an item, the checkbox does not appear.
However, if I set the default value of IsChecked to true, all of the list items appear with a checkbox and if I select an item and then deselect it, the checkbox correctly disappears.
What I am trying to achieve is that all of the items start off with no checkbox, selecting an item displays a checked checkbox, and deselecting an item hides the checkbox.
Any ideas where I am going wrong?
Manually set width of the first GridViewColumn to a fixed value. It seems ListView sets width of it to zero if it contains nothing and doesn't update width when checkboxes start to appear.
Alternatively, change code of BoolToVisibilityConverter to return Visibility.Hidden instead of Visibility.Collapsed.
I know this has been answered but I got around this problem using the following:
<GridViewColumn Header="MyColumn">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding MyItem, UpdateSourceTrigger=PropertyChanged}" ContentTemplate="{StaticResource myTemplate}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And in the Window I had a DataTemplate defined for the type that MyItem was:
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyItemViewModel}" x:Key="myTemplate" >
...template code
</DataTemplate>
</Window.Resources>

WPF: How to set column width with auto fill in ListView with custom user control

A ListView with Datatemplate in GridViewColumn:
<ListView Name ="LogDataList" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogDataCollection}" Background="Cyan">
<ListView.View>
<GridView AllowsColumnReorder="true"
ColumnHeaderToolTip="Event Log Information">
<GridViewColumn Header="Event Log Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<l:MyTextBlock Height="25" DataContext="{Binding LogName, Converter={StaticResource DataFieldConverter}}" HighlightMatchCase="{Binding Element}" Loaded="EditBox_Loaded"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
</GridView>
</ListView.View>
</ListView>
I have no idea about how to make column width autofill although I have tried a lot of way to walk up.
The general idea for demo is :
<ListView Name ="LogDataList" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogDataCollection}" Background="Cyan">
<ListView.Resources>
<Style x:Key="ColumnWidthStyle" TargetType="{x:Type GridViewColumn}">
<Style.Setters>
<Setter Property="HorizontalContentAlignment" Value="Stretch" >
</Setter>
</Style.Setters>
</Style>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="true"
ColumnHeaderToolTip="Event Log Information">
<GridViewColumn Header="Event Log Name" DisplayMemberBinding="{Binding Path=LogName}" HeaderContainerStyle="{StaticResource ColumnWidthStyle}">
It works, but not accord with my demand. I need to customize datatemplate with my custom user control(MyTextBlock) since the enhancement(HighlighMatchCase property) and binding datacontext.
How can I set up ColumnWidthMode with Fill in the word? On-line'in.
I really appreciate your help.
This is work for me. First, add Text property to MyTextBlock since it is not inherited from System.Windows.Controls.TextBlock but User Control.
public object Text
{
get { return GetValue(TextProperty); }
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(object),
typeof(MyTextBlock),
new PropertyMetadata(null, new PropertyChangedCallback(TextChangedCallback)));
static void TextChangedCallback(DependencyObject property,
DependencyPropertyChangedEventArgs args)
{
MyTextBlock textBox = (MyTextBlock)property;
textBox.textBlock.Text = args.NewValue.ToString();
}
Then, resize column width manually like this:
private void ResizeColumnWidth()
{
foreach (GridViewColumn column in LogGridView.Columns)
{
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}

Resources