how to edit listview row with a combo - wpf

I have a ListView with rows on it. When I click on a particular cell, I want that selected cell to be editable with a combo box and the thing is I have done it, but the combox box still remains even after editing. I want the combo box to change back to textblock.
<Style TargetType="{x:Type FrameworkElement}"
x:Key="GridEditStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility"
Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis},
ConverterParameter=True}" />
</Style>
<ComboBox SelectedItem="Present"
ItemsSource="{Binding ElementName=This,
Path=AvailablePublishers}"
Style="{StaticResource GridEditStyle}" />
code behind is
private ObservableCollection<string> _AvailablePublishers =
new ObservableCollection<string>();
public Student_Attendance()
{
InitializeComponent();
_AvailablePublishers.Add("Present");
_AvailablePublishers.Add("Absent");
_AvailablePublishers.Add("Late");
}
public ObservableCollection<string> AvailablePublishers
{ get { return _AvailablePublishers; } }
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
plz help me out
thank you

You know if you used a DataGrid, you would't have to do anything for switching the viewing and editing templates, it's done automatically ... here's a sample to get you started:
<DataGrid ItemsSource="{Binding ...}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding ...}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ElementName=This, Path=AvailablePublishers}" SelectedItem="{Binding ...}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Related

Binding all Texboxes in datagrid to list of string

i would like binding all textboxes from datagrid to list of string. I do not know how to do it.
I have textboxes in one column of datagrid:
<DataGrid ItemsSource="{Binding Data}">
<DataGridTextColumn Binding="{Binding Title}" Header="Title" IsReadOnly="True">
DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Width="60" Text="{Binding DataList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGridTextColumn>
</DataGrid>
In ViewModel:
public ObservableCollection<DataObject> Data //datasource
{
get { return _data;}
set { _data = value; OnPropertyChanged(nameof(Data)); }
}
public ObservableCollection<string> DataList //here must be data from textboxes
{
get { return _dataList; }
set { _dataList = value; OnPropertyChanged(nameof(DataList)); }
}
For simplicity I did not write here call of command. In action method of command is DataList property still empty.
Thanks
Try this :
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="False">
<DataGrid.Resources>
<local:CollectionToStringConverter x:Key="CollectionToStringConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Title" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.DataList,Converter={StaticResource CollectionToStringConverter},RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Write converter for converting collection of strings to one single string.
[ValueConversion(typeof(List<string>), typeof(string))]
public class CollectionToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a String");
return String.Join(", ", ((IList<string>)value).ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Thanks to WPF textblock binding with List

ListView inside ListView + control.Visibility

I'm creating a questionnaire app. My way of doing this is to create a ListView which contains question text and another ListView which contains list af answers(as RadioButtons).
The problem came when there are question which have an answer "Others" which require a TextBox for user to type some text. How can I achieve this? I mean i want to make TextBox visible only when collection of answers contains RadioButton with content "Other".
Below is my xaml code for ListView.
<ListView SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding OCquestions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20 0 20 0">
<TextBlock Text="{Binding Path=questionText}"/>
<ListView Name="ListaLista" SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding Path=listOfAnswer}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}" Content="{Binding Path=answerText}" Checked="RadioButton_Checked"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// HERE I WANT A TEXTBOX WHICH IS VISIBLE ONLY WHEN listOfAnswer collection contain a RadioButton with Content "Others"
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have no idea how to achieve this. I'm not familiar with Converters. Can anyone give me some tip ?
You need some Trigger to show/hide the TextBox, something like this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}"
Content="{Binding Path=answerText}"
Checked="RadioButton_Checked" Name="radio"/>
<TextBox Name="other" Visibility="Collapsed"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding answerText}" Value="Other">
<Setter TargetName="radio" Property="Content" Value=""/>
<Setter TargetName="other" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You can see that the DataTrigger listens to answerText, if it's "Other" just set the Content of Radio to empty string and set the TextBox's Visibility to Visible to show it. This TextBox will be shown on the right of the RadioButton.
First add a ValueConverter:
public abstract class BaseConverter : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class AnswerCollectionToVisibilityConverter : BaseConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ICollection<ListOfAnswers> answers = value as ICollection<ListOfAnswers>;
if (answers != null)
{
foreach (Answer answer in answers)
{
if (OtherRadioButtonIsHere)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add a TextBox that uses the ValueConverter to set the Visibility:
<TextBox Visibility="{Binding Path=listOfAnswer, Converter={AnswerCollectionToVisibilityConverter}}" />

Binding to attached property not working

I am trying to prepare template for DataGridColumnHeader. Template should be really simple.
Attached dependency property csdpp:CalendarProperties.EnumDay should only specific day that DataGridColumnHeader belong to and converter should then just return right label for that day.
If I set AncestorType to DataGridTextColumn (that is what I want) and leave the code like this:
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridTextColumn}, Mode=OneWay,
Path=(csdpp:CalendarProperties.EnumDay),
Converter={StaticResource IndexToDayLabelConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
nothing happen. Converter is not even getting called.
But if I change code to this:
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Mode=OneWay,
Path=(csdpp:CalendarProperties.EnumDay),
Converter={StaticResource IndexToDayLabelConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
(DatagridTextColumn switched by DataGrid, DataGrid has attached property too (below))
Converter is getting called and as value is passed attached property from DataGrid. Why is that working for DataGrid and not for DataGridTextColumn? Please help.
Code with DataGrid and DataGridTextColumn:
<DataGrid Grid.Row="1" Grid.Column="1"
x:Name="_shiftDataGrid"
ItemsSource="{Binding ElementName=Root, Path=PersonShiftgroupings.ShiftPersons}"
DataContext="{Binding ElementName=Root, Path=PersonShiftgroupings.ShiftPersons}"
AutoGenerateColumns="False"
csdpp:CalendarProperties.EnumDay="Fri">
<DataGrid.Columns>
<DataGridTextColumn
csdpp:CalendarProperties.EnumDay="Wed"
HeaderStyle="{StaticResource DayHeaderStyle}">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Any help would be appreciated.
Like i stated in my comment above DataGridColumnHeader is in the VisualTree of DataGrid via
DataGridColumnHeaderPresenter and not in the VisualTree of DataGridColumn.
You can reach the Column through DataGridColumnHeader's Column property
Column
I didn't want to get into your implementation and logic because i'm sure there's a cleaner way of doing what ever it is you need done.
Here's a sample of what you need to get it working :
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public List<SomeItem> MyItems
{
get { return new List<SomeItem> { new SomeItem() , new SomeItem() , new SomeItem() , new SomeItem() }; }
}
}
public class SomeItem
{
public int First { get { return 1; } }
public int Second { get { return 2; } }
public int Third { get { return 3; } }
public int Forth { get { return 4; } }
}
public static class ASample
{
public static string GetMyProperty(DependencyObject obj)
{
return (string)obj.GetValue(MyPropertyProperty);
}
public static void SetMyProperty(DependencyObject obj, string value)
{
obj.SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.RegisterAttached("MyProperty", typeof(string), typeof(ASample));
}
public class ColumnHeaderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
DataGridColumn c = (DataGridColumn)value;
string header = ASample.GetMyProperty(c);
return header;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML :
<Window.Resources>
<local:ColumnHeaderConverter x:Key="colConverter"/>
<Style TargetType="DataGridColumnHeader" x:Key="DayHeaderStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding
RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}, Mode=OneWay,
Path=Column , Converter={StaticResource colConverter} }"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid AutoGenerateColumns="False" ColumnHeaderStyle="{StaticResource DayHeaderStyle}"
ItemsSource="{Binding MyItems}" local:ASample.MyProperty="DataGrid" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding First}" local:ASample.MyProperty="11"/>
<DataGridTextColumn Binding="{Binding Second}" local:ASample.MyProperty="22"/>
<DataGridTextColumn Binding="{Binding Third}" local:ASample.MyProperty="33"/>
<DataGridTextColumn Binding="{Binding Forth}" local:ASample.MyProperty="44"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
DataGridTextColumn is no ancestor of DataGridColumnHeader

WPF DataGrid column with variable number of buttons/shapes

I have a requirement where on one DataGrid column, I need to show x number of clickable buttons side by side (horizontally stacked). The actual number or buttons to show depends on the binded value in that column.
The image below shows this. The left hand grid is my current one and and the right hand grid is what I am looking for.
Grid is binded to ViewModel like:
<DataGrid ItemsSource="{Binding employees}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=id}" Header="Id" />
<DataGridTextColumn Binding="{Binding Path=numberofplatforms}" Header="No" Width="50" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
And ViewModel is:
Public Class ViewModel
Public Property employees As ObservableCollection(Of employee)
Get
Return _employees
End Get
Set(ByVal value As ObservableCollection(Of employee))
_employees = value
End Set
End Property
Private _employees As New ObservableCollection(Of employee)
End Class
And the employee is:
Public Class employee
Public Property id As String
Public Property numberofplatforms As Integer
Public Property selectedplatform As Integer
End Class
On top of just displaying the buttons, the buttons themselve must act like radiobuttons, i.e. on any DataGrid row, only one button is "pushed down" (blue background buttons in the image) and the others are not pushed (gray background). Buttons (or shapes) must be clickable so that the selection can be changed.
Which button is "pushed" down is determined from the ViewModel, according to selectedplatform property. That property in this example is 1 for the ABC, 1 for the DEF and 2 for the GHI. If the numberofplatforms property is zero, no buttons are displayed (JKL).
How can I set up this mechanism?
Some parts of the code are in C#, I hope it's not a problem. Wpf code:
<Window.Resources>
<ResourceDictionary>
<local:PositiveIntegersConverter x:Key="PositiveIntegersConverter" />
<local:EmployeePlatformOptionConverter x:Key="EmployeePlatformOptionConverter"/>
</ResourceDictionary>
</Window.Resources>
<DataGrid ItemsSource="{Binding employees}" AutoGenerateColumns="False" x:Name="DataGrid1">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=id}" Header="Id" />
<DataGridTemplateColumn Header="No" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding numberofplatforms, Converter={StaticResource PositiveIntegersConverter}}" SelectedItem="{Binding selectedplatform}"
x:Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Command="{Binding Source={x:Reference DataGrid1}, Path=DataContext.SelectOptionCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource EmployeePlatformOptionConverter}">
<Binding ElementName="ListBox1" Path="DataContext"/>
<Binding Path="."/>
</MultiBinding>
</Button.CommandParameter>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Gray" />
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Path=IsSelected}"
Value="True">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I used two converters here:
PositiveIntegersConverter which returns positive integers for a given numberofplatforms - these are available platform options for an employee
EmployeePlatformOptionConverter which converts two parameters that we want to pass to SelectOptionCommand: employee and selected platform option into one object of EmployeePlatformOption type.
public class PositiveIntegersConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var toInteger = System.Convert.ToInt32(value);
return toInteger > 0 ? Enumerable.Range(1, toInteger) : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class EmployeePlatformOptionConverter
: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new EmployeePlatformOption
{
Employee = values[0] as Employee,
Platform = (int)values[1]
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Using encapsulating object:
public class EmployeePlatformOption
{
public Employee Employee { get; set; }
public int Platform { get; set; }
}
you pass selection to SelectOptionCommand in your ViewModel class:
public ICommand SelectOptionCommand { get; set; }
private void SelectOptionExecute(EmployeePlatformOption employeePlatformOption)
{
if (employeePlatformOption != null && employeePlatformOption.Employee != null &&
employeePlatformOption.Platform > 0)
{
employeePlatformOption.Employee.selectedplatform = employeePlatformOption.Platform;
}
}
Employee should implement INotifyPropertyChange interface so selectedplatform update is visible on the screen.

Highlight item in ItemsControl

I want to render a couple of elements using an ItemsControl, and highlight one of them
My ViewModel:
public class ViewModel
{
public List<Item> Items;
public Item HighlightedItem;
}
My XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<myUserControl Background="{?}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I want to highlight the item by setting the background property to something specific, how should I go on about it?
First have a converter which will compare reference of two objects say ObjectEqualsConverter
public class ObjectEqualsConverter : IMultiValueConverter
{
#region IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
And in XAML file, use converter to check if current item is same as highlighted item in ViewModel and in case converter returns true set the color of control using trigger-
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<myUserControl x:Name="myControl" />
<DataTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ObjectEqualsConverter}">
<Binding/>
<Binding Path="DataContext.HighlightedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="myControl" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Make sure you add converter as a resource in your xaml file.

Resources