I'm trying to create a WPF dialogue to configure a workshop schedule.
On the left side there is a listview with one column for the teachers' names.
On the right side there is a listview with six columns, one for workshops,
and the other five columns for the days from Monday to Friday.
| Teacher | | Workshop | Mon | Tue | Wed | Thu | Fri |
A WS-1 A B
B WS-2 C
C WS-3 B
The user should configure the schedule by drag and drop, e.g. teacher C teaches
workshop WS-2 on Monday.
In WinForms it was a simple job getting the drop cell, but I don't find a way
of getting this in WPF.
Here's a section of the xaml code.
<ListView Name="_LV_Teacher"
SelectionMode="Single"
ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=Id}"
Width="0"/>
<GridViewColumn
DisplayMemberBinding="{Binding Path=TeachersName}" Header="Teacher"/>
</GridView>
</ListView.View>
</ListView>
<ListView Name="_LV_Schedule"
ItemsSource="{Binding}"
AlowDrop="True"
Drop="_LV_Teacher_Drop" >
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=WorkshopId}" Width="0"/>
<GridViewColumn Header="Monday"/>
<GridViewColumn Header="Tuesday"/>
<GridViewColumn Header="Wednesday"/>
<GridViewColumn Header="Thursday"/>
<GridViewColumn Header="Friday"/>
</GridView>
</ListView.View>
</ListView>
DragDrop HowTo
Challange was to figure out which property should be used in DragDrop.DoDragDrop(). As we have different ListView , so using Teacher entity itself would be meaningful. I get the source Teacher from left side ListView using TextBlock.DataContext. And set it as DataContext of the Drop target TextBlock.
Issue was , which DataFormats value to use for DataContext, so I used IDataObject.GetFormats().
Output :
See the sample application below. You can simple copy/paste.
Window1.xaml
<Window x:Class="WpfStackOverflow.MyDragDrop.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfStackOverflow.MyDragDrop"
Title="Window1" Height="472.557" Width="675.564">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="123*"/>
<ColumnDefinition Width="380*"/>
</Grid.ColumnDefinitions>
<ListView Margin="10,31,10,226" ItemsSource="{Binding TeacherData}">
<ListView.Resources>
<Style x:Key="TbKey" TargetType="TextBlock">
<EventSetter Event="MouseLeftButtonDown" Handler="Teacher_LeftButtonDown"/>
<EventSetter Event="DragEnter" Handler="Teacher_DragEnter"/>
<Setter Property="Background" Value="Yellow"/>
<Setter Property="AllowDrop" Value="True"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Teachers" Width="75">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Width="{Binding View.Columns[0].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<ListView ItemsSource="{Binding AppointmentData}" Margin="13,31,10,226" Grid.Column="1" ScrollViewer.CanContentScroll="False">
<ListView.Resources>
<Style x:Key="TbKey" TargetType="TextBlock">
<EventSetter Event="MouseLeftButtonDown" Handler="WeekDay_LeftButtonDown"/>
<EventSetter Event="DragEnter" Handler="WeekDay_DragEnter"/>
<EventSetter Event="Drop" Handler="WeekDay_Drop"/>
<EventSetter Event="MouseRightButtonDown" Handler="WeekDay_MouseRightButtonDown"/>
<Setter Property="Background" Value="Yellow"/>
<Setter Property="AllowDrop" Value="True"/>
<Setter Property="DataContext" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="Background" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Workshop" Width="75">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Workshop}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="ColMon" Header="Mon" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Mon, Mode=TwoWay}" Text="{Binding Name}" Width="{Binding View.Columns[1].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Tue" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Tue, Mode=TwoWay}" Text="{Binding Name}" Width="{Binding View.Columns[1].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Wed" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Wed, Mode=TwoWay}" Text="{Binding Name}" Width="{Binding View.Columns[1].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Thu" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Thu, Mode=TwoWay}" Text="{Binding Name}" Width="{Binding View.Columns[1].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Fri" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Fri, Mode=TwoWay}" Text="{Binding Name}" Width="{Binding View.Columns[1].Width, RelativeSource={RelativeSource AncestorType=ListView, Mode=FindAncestor}}"
Style="{StaticResource TbKey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<DataGrid IsReadOnly="True" AutoGenerateColumns="False" ItemsSource="{Binding AppointmentData}" Grid.Column="1" HorizontalAlignment="Stretch" Margin="13,243,0,0" VerticalAlignment="Top" >
<DataGrid.Columns>
<DataGridTextColumn Header="Worskhop" Binding="{Binding Workshop}" Width="*"/>
<DataGridTextColumn Header="Mon" Binding="{Binding Mon.Name}" Width="*"/>
<DataGridTextColumn Header="Tue" Binding="{Binding Tue.Name}" Width="*"/>
<DataGridTextColumn Header="Wed" Binding="{Binding Wed.Name}" Width="*"/>
<DataGridTextColumn Header="Thu" Binding="{Binding Thu.Name}" Width="*"/>
<DataGridTextColumn Header="Fri" Binding="{Binding Fri.Name}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Margin="13,222,0,0" TextWrapping="Wrap" Text="Real time display of workshop schedules" VerticalAlignment="Top" Width="325"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Margin="13,10,0,0" TextWrapping="Wrap" Text="Right Click to remove appointment" VerticalAlignment="Top" Width="325"/>
</Grid>
</Window>
Window1.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfStackOverflow.MyDragDrop
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
ViewModel vm = new ViewModel();
public Window1()
{
InitializeComponent();
this.DataContext = vm;
}
#region WeekDay
private void WeekDay_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effects = DragDropEffects.Copy;
}
private void WeekDay_Drop(object sender, DragEventArgs e)
{
((TextBlock)sender).DataContext = e.Data.GetData(e.Data.GetFormats()[0]);
}
private void WeekDay_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
(sender as TextBlock).DataContext = null;
}
#endregion
#region Teacher
private void Teacher_LeftButtonDown(object sender, MouseButtonEventArgs e)
{
TextBlock tb = (TextBlock)sender;
DragDrop.DoDragDrop(tb, tb.DataContext, DragDropEffects.Copy | DragDropEffects.Move);
}
private void Teacher_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Serializable))
e.Effects = DragDropEffects.Copy;
}
#endregion
}
public class ViewModel : INotifyPropertyChanged
{
ObservableCollection<AppointmentRecord> _records = new ObservableCollection<AppointmentRecord>();
public ObservableCollection<AppointmentRecord> AppointmentData { get { return _records; } }
ObservableCollection<TeacherRecord> _teacherRecords = new ObservableCollection<TeacherRecord>();
public ObservableCollection<TeacherRecord> TeacherData { get { return _teacherRecords; } }
public ViewModel()
{
TeacherRecord trecord1 = new TeacherRecord() { Name = "A" };
TeacherRecord trecord2 = new TeacherRecord() { Name = "B" };
TeacherRecord trecord3 = new TeacherRecord() { Name = "C" };
TeacherData.Add(trecord1);
TeacherData.Add(trecord2);
TeacherData.Add(trecord3);
AppointmentRecord record1 = new AppointmentRecord() { Workshop = "WS-1", Mon = TeacherData.FirstOrDefault((t) => { return t.Name == "A"; }), Tue = null };
AppointmentRecord record2 = new AppointmentRecord() { Workshop = "WS-2", Mon = null, Tue = TeacherData.FirstOrDefault((t) => { return t.Name == "C"; }) };
AppointmentRecord record3 = new AppointmentRecord() { Workshop = "WS-3", Mon = null, Tue = null, Wed = TeacherData.FirstOrDefault((t) => { return t.Name == "C"; }) };
AppointmentData.Add(record1);
AppointmentData.Add(record2);
AppointmentData.Add(record3);
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string prop)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public class TeacherRecord
{
public string Name { get; set; }
}
public class AppointmentRecord : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string prop)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
string _workshop;
public string Workshop
{
get { return _workshop; }
set
{
if (_workshop != value)
{
_workshop = value;
OnPropertyChanged("Workshop");
}
}
}
TeacherRecord _mon;
public TeacherRecord Mon
{
get { return _mon; }
set
{
if (_mon != value)
{
_mon = value;
OnPropertyChanged("Mon");
}
}
}
TeacherRecord _tue;
public TeacherRecord Tue
{
get { return _tue; }
set
{
if (_tue != value)
{
_tue = value;
OnPropertyChanged("Tue");
}
}
}
TeacherRecord _wed;
public TeacherRecord Wed
{
get { return _wed; }
set
{
if (_wed != value)
{
_wed = value;
OnPropertyChanged("Wed");
}
}
}
TeacherRecord _thu;
public TeacherRecord Thu
{
get { return _thu; }
set
{
if (_thu != value)
{
_thu = value;
OnPropertyChanged("Thu");
}
}
}
TeacherRecord _fri;
public TeacherRecord Fri
{
get { return _fri; }
set
{
if (_fri != value)
{
_fri = value;
OnPropertyChanged("Fri");
}
}
}
}
}
Thanks to AnjumSKhan for the excellent answer!
I've implemented it into my application, and it works fine. Unfortunately, I got another problem.
Because my data are retrieved and stored in a database, I added two variables:
- int iTeacherId to class TeacherRecord (first column with width 0 in the teacher's Listview), and
- int iWorkShopId to class AppointmentRecord (first column with width 0 in the AppointmentData-Listview)
Therefore, beside the teacher's name, e.Data in WeekDay_Drop now also contains his Id.
But how do I get the corresponding workshop Id in order to save appointment in Tab_Workshop in the database.
(The teacher's name and Id are in Tab_Teacher, a different database table.)
Related
I have a listview with 5 columns. In the first column i insert an image in wich i have an event MouseClicked event. What i want to accomplish is when i click the image from row 5 , to get the name of item in row 5 - column 2;
This is my code so far :
<ListView x:Name="HistoryListB">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="#FF515050"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="true">
<Setter Property="Background" Value="#FF515050"/>
<Setter Property="BorderBrush" Value="#FF3B3A3A"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridViewColumn Width="90" Header="Image" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Name="Favorite" Width="12" Height="12" Source="{Binding ImageSource}" MouseLeftButtonDown="Image_MouseLeftButtonDown" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="90" Header="Time Added" DisplayMemberBinding="{Binding Time}" />
<GridViewColumn Width="110" Header="Status" DisplayMemberBinding="{Binding Status}"/>
<GridViewColumn Width="290" Header="ItemTitle" DisplayMemberBinding="{Binding ItemTitle}"/>
<GridViewColumn Width="50" Header="Duration" DisplayMemberBinding="{Binding Duration}"/>
</GridView>
</ListView.View>
</ListView>
And i add items in listview like this. I have a class and a list like this :
public class HistoryItems
{
public string Time { get; set; }
public string Status { get; set; }
public string ItemTitle { get; set; }
public string Duration { get; set; }
public ImageSource ImageSource { get; set; }
}
IList<HistoryItems> SHistoryItems { get; set; }
And then add the items :
SHistoryItems = new List<HistoryItems>() {
new HistoryItems () {
Time = DateTime.Now.ToString("HH:mm:ss tt"),
Status = "Started Playing : ",
ItemTitle = StationL.StationName
}
};
foreach (var item in SHistoryItems)
HistoryListB.Items.Add(SHistoryItems);
And in my code i want to get the name of the item from same row but 2nd column :
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TextBlock1.Text = ?? THE NAME OF THE SECOND COLUMN IN THE SAME ROW ?? ;
}
You can use the Tag property of the Image to hold the HistoryItem then you can access through the sender argument in the event handler.
Example:
Xaml:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Name="Favorite" Width="12" Height="12" Source="{Binding ImageSource}" Tag="{Binding}" MouseLeftButtonDown="Image_MouseLeftButtonDown" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Code:
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var image = sender as Image;
if (image != null && image.Tag is HistoryItems)
{
TextBlock1.Text = (image.Tag as HistoryItems).ItemTitle;
}
}
I have a WPF ListView with checkbox in MVVM pattern. I need to accomplish the following two tasks.
1) Sort by any column when I click on column header. If column is already in ascending order then it should reorder in descending and vice versa.
2) SelectedTaskItem is not communicating with ViewModel when i check or uncheck a checkbox.
TaskView.xaml
<UserControl x:Class="MyProject.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="569" Width="954"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
>
<UserControl.Resources>
<DataTemplate x:Key="FirstCellCheckBox">
<CheckBox
Command="{Binding IsSelected, Mode= TwoWay}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ListViewItem}}}"
CommandParameter="{Binding Path=SelectedTaskItem,
ElementName=dgTaskList, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</UserControl.Resources>
<ListView Grid.Row="1"
Name="ListViewTask"
Margin="12,49,26,79"
ItemsSource="{Binding TaskList}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
>
<ListView.View >
<GridView x:Name="gvTaskList">
<GridViewColumn Header="Select"
CellTemplate="{StaticResource FirstCellCheckBox}"
Width="30"/>
<GridViewColumn Header="Internal File"
DisplayMemberBinding="{Binding TaskID}"
Width="100"/>
<GridViewColumn Header="TaskDescription"
DisplayMemberBinding="{Binding TaskDescription}"
Width="100" />
<GridViewColumn Header="Task Status"
DisplayMemberBinding="{Binding TaskStatus}"
Width="100" />
</GridView>
</ListView.View>
</ListView>
TaskViewModel.cs
namespace MyProject
{
public class TaskViewModel: ViewModelBase
{
ObservableCollection<TaskModel> _TaskList;
public TaskViewModel()
{
TaskDAO dal = new TaskDAO();
_TaskList= dal.GetUpFileList();
}
public ObservableCollection<TaskModel> TaskList
{
get { return _TaskList; }
set
{
if (_TaskList!= value)
{
this._TaskList= value;
this.OnPropertyChanged("TaskList");
}
}
}
private TaskModel _selectedTaskItem;
public TaskModel SelectedTaskItem
{
get { return _selectedTaskItem; }
set
{
if (value != null)
{
_selectedTaskItem= value;
OnPropertyChanged("SelectedTaskItem");
if (null != _selectedTaskItem)
{
ObservableCollection<TaskModel> oCol =
new ObservableCollection<TaskModel>();
foreach (TaskModel itm in TaskList)
{
if (itm.TaskID == _selectedTaskItem.TaskID)
{
itm.IsSelected = true;
}
oCol.Add(itm);
}
TaskList.Clear();
TaskList = oCol;
OnPropertyChanged("TaskList");
}
}
}
}
}
You are binding the CheckBox's IsChecked value to ListBoxItem.IsChecked, but I don't see anything that binds ListBoxItem.IsChecked to your ViewModel.
Try adding the following to your ListBox.Resources
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
For sorting, I'd recommend using a DataGrid instead of a ListView, since sorting is built into the DataGrid. If you don't want to do that, you'll probably have to make some custom ListViewHeaders which execute a SortCommand in your ViewModel
i have a listview template and one column is a button. I need selected item when i click in this button. How i can do this ??
To cature the selected ListView item inside a button pressed event you can leverage the MVVM pattern. In my ListView, in the XAML, I bind the ItemsSource and SelectedItem to a ViewModel class. I also bind my button Command in the template to RunCommand in the ViewModel.
The tricky part is getting the binding correct from the template to the active DataContext.
Once you do this you can capture the SelectedCustomer inside the RunCommand that
gets executed when the button gets pressed.
I've included some of the code to help get you started.
You can find implementations of ViewModelBase and DelegateCommand via Google.
Here is the XAML:
<Window x:Class="ListViewScrollPosition.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="400">
<Grid>
<ListView ItemsSource="{Binding Path=Customers}"
SelectedItem="{Binding Path=SelectedCustomer}"
Width="Auto">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Last Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Address">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<Button Content="Address"
Command="{Binding
Path=DataContext.RunCommand,
RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Here is the ViewModel:
using System.Collections.ObjectModel;
using System.Windows.Input;
using ListViewScrollPosition.Commands;
using ListViewScrollPosition.Models;
namespace ListViewScrollPosition.ViewModels
{
public class MainViewModel : ViewModelBase
{
public ICommand RunCommand { get; private set; }
public MainViewModel()
{
RunCommand = new DelegateCommand<object>(OnRunCommand, CanRunCommand);
_customers = Customer.GetSampleCustomerList();
_selectedCustomer = _customers[0];
}
private ObservableCollection<Customer> _customers =
new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customers
{
get
{
return _customers;
}
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get
{
return _selectedCustomer;
}
set
{
_selectedCustomer = value;
OnPropertyChanged("SelectedCustomer");
}
}
private void OnRunCommand(object obj)
{
// use the SelectedCustomer object here...
}
private bool CanRunCommand(object obj)
{
return true;
}
}
}
Here is where I link in the ViewModel to the View:
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
Example with a regular click event in the code behind:
<ListView Height="167.96" VerticalAlignment="Top" ItemsSource="{Binding FulfillmentSchedules}" SelectedItem="{Binding SelectedFulfillmentSchedule}">
<ListView.View>
<GridView>
<GridViewColumn Header="Request">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}-{1}-{2}">
<Binding Path="Template.ProjectNumber" />
<Binding Path="Template.JobNumber" />
<Binding Path="Template.RequestId" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Template" DisplayMemberBinding="{Binding Template.Name}"/>
<GridViewColumn Header="Start Date" DisplayMemberBinding="{Binding StartDate}"/>
<GridViewColumn Header="Records" DisplayMemberBinding="{Binding Parameters.Records}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Name="BtnYourButton" Content="Your Button" Click="BtnYourButton_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code behind:
private void BtnYourButton_Click(object sender, RoutedEventArgs e)
{
var boundData= (YourBoundDataType)((Button)sender).DataContext;
//do what you need to do here, including calling other methods on your VM
}
Note: While I certainly appreciate MVVM, I've come to accept that there is a pretty steep slope of dimminishing returns once you cross into actions and messaging between the form and the VM, so I use it only in cases of complex relationships between VMs or large singular VMs. For CRUD style data-centric applications I prefer to handle actions and message relay with the code behind.
I'm still learning WPF, but I'm really confused about something that should be really simple. What I want to do is to center the contents of the 3rd and 4th columns. When I run this, the columns are left justified:
<ListView Margin="0" x:Name="listMonitoredUrls" AlternationCount="1"
ItemsSource="{Binding}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Description" DisplayMemberBinding="{Binding FriendlyDesc}"/>
<GridViewColumn Header="Url" DisplayMemberBinding="{Binding Url}"/>
<GridViewColumn Header="Frequency">
<GridViewColumn.CellTemplate >
<DataTemplate>
<TextBlock Text="{Binding ScanFrequencyMinutes}"
HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Next Scan" >
<GridViewColumn.CellTemplate >
<DataTemplate>
<TextBlock Text="{Binding TimeNextScanStr}"
HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I"m really starting to like WPF, but some simple things like this seem to be really hard.
Try using the TextAlignment property instead of HorizontalAlignment - should do it.
To my understanding HorizontalAlignment="Center" will center your textblock not the text in it.
This might be a long shot but i've had to do it for listboxes where the items are defined by templates. Try setting the HorizontalContentAlignment="Stretch" on your ListView. If I don't set that the items take only as much space as they need and are left justified.
I've created a solution which works under the common scenario of:
<GridViewColumn Header="Some Property" DisplayMemberBinding="{Binding SomeProperty}" />
where one only wants a simple DisplayMemberBinding with text without having to specify a CellTemplate
the new code uses an attached property and becomes:
<GridViewColumn Header="Some Property" DisplayMemberBinding="{Binding SomeProperty}"
ctrl:GridViewExtensions.IsContentCentered="True" />
attached property code:
public static class GridViewExtensions
{
#region IsContentCentered
[Category("Common")]
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static bool GetIsContentCentered(GridViewColumn gridViewColumn)
{
return (bool)gridViewColumn.GetValue(IsContentCenteredProperty);
}
public static void SetIsContentCentered(GridViewColumn gridViewColumn, bool value)
{
gridViewColumn.SetValue(IsContentCenteredProperty, value);
}
public static readonly DependencyProperty IsContentCenteredProperty =
DependencyProperty.RegisterAttached(
"IsContentCentered",
typeof(bool), // type
typeof(GridViewExtensions), // containing type
new PropertyMetadata(default(bool), OnIsContentCenteredChanged)
);
private static void OnIsContentCenteredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
OnIsContentCenteredChanged((GridViewColumn)d, (bool)e.NewValue);
}
private static void OnIsContentCenteredChanged(GridViewColumn gridViewColumn, bool isContentCentered)
{
if (isContentCentered == false) { return; }
// must wait a bit otherwise GridViewColumn.DisplayMemberBinding will not yet be initialized,
new DispatcherTimer(TimeSpan.FromMilliseconds(100), DispatcherPriority.Normal, OnColumnLoaded, gridViewColumn.Dispatcher)
{
Tag = gridViewColumn
}.Start();
}
static void OnColumnLoaded(object sender, EventArgs e)
{
var timer = (DispatcherTimer)sender;
timer.Stop();
var gridViewColumn = (GridViewColumn)timer.Tag;
if (gridViewColumn.DisplayMemberBinding == null)
{
throw new Exception("Only allowed with DisplayMemberBinding.");
}
var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
textBlockFactory.SetBinding(TextBlock.TextProperty, gridViewColumn.DisplayMemberBinding);
textBlockFactory.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Center);
var cellTemplate = new DataTemplate { VisualTree = textBlockFactory };
gridViewColumn.DisplayMemberBinding = null; // must null, otherwise CellTemplate won't be recognized
gridViewColumn.CellTemplate = cellTemplate;
}
#endregion IsContentCentered
}
Here is my example to show a working xaml:
<Window x:Class="WPF_Tutorial.Rich_text_controls.BlockUIContainerCenteredColumnSample"
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"
mc:Ignorable="d"
xmlns:self="clr-namespace:WPF_Tutorial.Rich_text_controls"
Title="BlockUIContainerCenteredColumnSample" Height="275" Width="300"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<x:Array x:Key="UserArray" Type="{x:Type self:User}">
<self:User Name="John Doe" Age="42" />
<self:User Name="Jane May-Anne Josephine Renalds Doe" Age="36" />
</x:Array>
</Window.Resources>
<Grid>
<FlowDocumentScrollViewer>
<FlowDocument>
<Paragraph FontSize="36" Margin="0">Users</Paragraph>
<Paragraph FontStyle="Italic" TextAlignment="Left" FontSize="14" Foreground="Gray">Here's a list of our users, inside our FlowDocument, in a completely interactive ListView control!</Paragraph>
<BlockUIContainer>
<ListView BorderThickness="0" ItemsSource="{StaticResource UserArray}" HorizontalAlignment="Center">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- This stretches out the TextBlock width to the column width -->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="150" />
<GridViewColumn>
<GridViewColumnHeader Content="Age" Width="75" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Age}" TextAlignment="Center" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</BlockUIContainer>
<Paragraph FontStyle="Italic" TextAlignment="Left" FontSize="14" Foreground="Gray">More content can go here...</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
</Grid>
</Window>
Notice the <ListView.ItemContainerStyle> block. It has the <Setter ....
Without this, as per AndyG's text, nothing will work the way you want.
This has been very frustrating trying to work out.
By the way, here is the backing-code for this xaml:
namespace WPF_Tutorial.Rich_text_controls
{
using System.Windows;
public partial class BlockUIContainerCenteredColumnSample : Window
{
public BlockUIContainerCenteredColumnSample()
{
InitializeComponent();
}
}
public class User
{
public int Age { get; set; }
public string Name { get; set; }
}
}
What you should see when run
I'm trying to display information from my ObservableCollection<MyData> in a ListView. MyData has:
string Name
string Location
int Progress
Using DataBinding, I'm able to display the Name and Location for all the items in my ObservableCollection<MyData> in their own column. But how can I add a Progress column with a ProgressBar inside? Progress is a percentage.
<ListView ItemsSource="{Binding PersonList}">
<ListView.View>
<GridView>
<GridViewColumn Width="140" Header="GameName" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="140" Header="Progress">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ProgressBar Maximum="100" Value="{Binding Progress}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Your ListView in XAML:
<ListView x:Name="DataView">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=Name}" />
<ProgressBar Height="20" Width="100" Value="{Binding Path=Progress}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code-behind:
internal class MyData
{
public string Name { get; set; }
public int Progress { get; set; }
}
...
var items = new ObservableCollection<MyData>();
items.Add(new MyData() { Name = "Some", Progress = 25 });
items.Add(new MyData() { Name = "Another", Progress = 91 });
DataView.ItemsSource = items;
Just bind Progress property in MyData to the ProgressBar.Value and set the expected MAX value as the ProgressBar.Maximum for example 50 below
<ProgressBar Maximum="50" Value="{Binding Progress}" ..