Making a Button Visible in DataGrid - c#

i've created a Datagrid Control in WPF. how can i make the Button visible only for
row that i have select it so that my Button will be shown in the Button-Column-Cell.
XAML:
<DataGridTemplateColumn x:Name="Button-Column" Header="H." Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=ObjectType}" Value="E">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ObjectType}" Value="F">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ObjectType}" Value= "P">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
<Button Name="btnTable" Visibility="{Binding Path=ObjectType}" Height="20"
Width="25" Click="Button_Table_Click">
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Here is a solution using multibinding :
xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication8="clr-namespace:WpfApplication8"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<WpfApplication8:ButtonVisibilityConverter x:Key="buttonVisibilityConverter"/>
</Window.Resources>
<DataGrid x:Name="dataGrid" SelectedItem="{Binding MySelectedItem}" ItemsSource="{Binding MyObjectList}" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Header="H." Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Height="20"
Width="25">
<Button.Visibility>
<MultiBinding Converter="{StaticResource buttonVisibilityConverter}">
<Binding Path="Parent.MySelectedItem"/>
<Binding></Binding>
</MultiBinding>
</Button.Visibility>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
Code behind :
namespace WpfApplication8
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public class Toto
{
public object Parent { get; set; }
public string Name { get; set; }
public string Content { get; set; }
public Toto(string name, string content, object parent)
{
this.Name = name; this.Content = content; this.Parent = parent;
}
}
private object _mySelectedItem;
public object MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MySelectedItem"));
}
}
}
private List<Toto> _myObjectList;
public List<Toto> MyObjectList
{
get { return _myObjectList; }
set { _myObjectList = value; }
}
public MainWindow()
{
this.MyObjectList = new List<Toto>
{
new Toto("toto1", "content toto1", this),
new Toto("toto2", "content toto2", this)
};
InitializeComponent();
this.DataContext = this;
}
}
}
Converter :
class ButtonVisibilityConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == values[1])
{
return Visibility.Visible;
}
else
{
return Visibility.Hidden;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

Related

WPF - Changes in treeviewitems' properties not reflected in UI problem

I have a tree view built with HierarchicalDataTemplates, I want to be able to add JSON files to SegmentInfo nodes - if I do so, the data is added but the change is not reflected in UI (still the comment says "no data" in red).
I've made the list the tree view items as ObservableCollection, moved it to a "ViewModel" class that inherits INotifyPropertyChanged, I seem to set it up properly, I've set DataContext to the ViewModel object in my Window.
In xaml I've set the bindings and mode as TwoWay. Still nothing helped
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
TypeInfo class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
SegmentInfo class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
Converter classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
I expect that after successful adding json file to the SegmentInfo the UI will update the node with "has data" comment.
Now I can check that the data is really added to the SegmentInfo but UI doesn't reflect that.
Your HasData property does not update the UI, as you have no mechanism to update it (INotifyPropertyChanged). SegmentInfo needs to implement INotifyPropertyChanged.
If you plan to have a property Bind to the UI, it needs to have an PropertyChanged Notification go out for it individually. So on your SegmentInfo class; Name, HasData, and JsonContent should each raise an OnPropertyChanged event in their setter.
A good way to think of it; anything that is directly bound in XAML (Text="{Binding Name}") should raise an event when changed. If you bind any properties like: (Text="{Binding MyThing.Name}") you will not get an update when MyThing.Name changes. You need to break out the property and Notify on it directly.

How to bind ListViewItem's IsSelected to property in Style

I'm trying to highlight a row in my ListView based on a property of class. The ItemSource is set to an ObservableCollection<FileInformation> the property I need to determine whether the row should be highlighted is contained in the FileInformation class - bool IsPlaying.
This is my ListView xaml:
<ListView Name="lvListView" Margin="0,0,0,35" AllowDrop="True" ItemsSource="{Binding FilesCollection}">
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding ListViewFileDrop}" PassEventArgsToCommand="True"/>
</ia:EventTrigger>
<ia:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding ListViewDoubleClickCommand}" PassEventArgsToCommand="True"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Command="{Binding RemoveCommand}" CommandParameter="{x:Reference Name=lvListView}"/>
<MenuItem Header="Add File" Command="{Binding BrowseCommand}"/>
<MenuItem Header="Clear All" Command="{Binding ClearAllCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding RelativeSource={RelativeSource Self}, Path=IsPlaying, Converter={converters:TestConverter}}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Title" Width="350" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Time" Width="114"
DisplayMemberBinding="{Binding Time, Converter={converters:TimeSpanFormatConverter}}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
This is the part I'm struggling with:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding RelativeSource={RelativeSource Self}, Path=IsPlaying, Converter={converters:TestConverter}}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
Right now in order to highlight the row I need to click on it, I want it to be highlighted in yellow only when the property IsPlaying is set to true.
This is the property declaration:
public static readonly DependencyProperty IsPlayingProperty =
DependencyProperty.Register(nameof(IsPlaying), typeof(bool), typeof(FileInformation),
new PropertyMetadata(null));
private bool _isPlaying = false;
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set
{
SetValue(IsPlayingProperty, value);
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
See if this helps at all. Take a look at the converters XAML (both in the binding and in Window.Resources).
I added a read only datagrid to see the raw data and a button to toggle (true/false) a specific FileInformation.IsPlaying.
MainWindow.xaml
<Window x:Class="WpfApp13.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp13"
xmlns:converters="clr-namespace:WpfApp13"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="525">
<Window.Resources>
<converters:TestConverter x:Key="TestConverter" />
<converters:TimeSpanFormatConverter x:Key="TimeSpanFormatConverter" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Since it looks like you want to play audio, I set SelectionMode to Single. Probably don't want to play multiple at once -->
<ListView Name="lvListView" Margin="0,0,0,35" AllowDrop="True" ItemsSource="{Binding FilesCollection}" SelectionMode="Single">
<!--<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding ListViewFileDrop}" PassEventArgsToCommand="True"/>
</ia:EventTrigger>
<ia:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding ListViewDoubleClickCommand}" PassEventArgsToCommand="True"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>-->
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Command="{Binding RemoveCommand}" CommandParameter="{x:Reference Name=lvListView}"/>
<MenuItem Header="Add File" Command="{Binding BrowseCommand}"/>
<MenuItem Header="Clear All" Command="{Binding ClearAllCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Beige"/>
<!-- The converter, TestConverter, isn't even needed but I'll leave it since you might be doing something else with it -->
<Setter Property="IsSelected" Value="{Binding Path=IsPlaying, Converter={StaticResource TestConverter}}"/>
<Style.Triggers>
<!--<Trigger Property="IsSelected" Value="True">-->
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Title" Width="350" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Time" Width="114"
DisplayMemberBinding="{Binding Time, Converter={StaticResource TimeSpanFormatConverter}}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<DataGrid Grid.Row="1" AutoGenerateColumns="False" ItemsSource="{Binding FilesCollection}" IsReadOnly="True">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsPlaying}" Header="IsPlaying" />
<DataGridTextColumn Binding="{Binding Title}" Header="Title" />
<DataGridTextColumn Binding="{Binding Time}" Header="Time" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Toggle File 5 IsPlaying" Grid.Row="2" Margin="10" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WpfApp13
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((MainWindowViewModel)DataContext).PlayToggle(4); // I'm too lazy to use an ICommand...
}
}
public class MainWindowViewModel : DependencyObject
{
public ObservableCollection<FileInformation> FilesCollection
{
get { return (ObservableCollection<FileInformation>)GetValue(FilesCollectionProperty); }
set { SetValue(FilesCollectionProperty, value); }
}
public static readonly DependencyProperty FilesCollectionProperty = DependencyProperty.Register("FilesCollection", typeof(ObservableCollection<FileInformation>), typeof(MainWindowViewModel), new PropertyMetadata(null));
public MainWindowViewModel()
{
FilesCollection = new ObservableCollection<FileInformation>() {
new FileInformation() { Title = "File 1", IsPlaying = false, Time = new TimeSpan(0, 5, 1) },
new FileInformation() { Title = "File 2", IsPlaying = false, Time = new TimeSpan(0, 5, 2) },
new FileInformation() { Title = "File 3", IsPlaying = false, Time = new TimeSpan(0, 5, 3) },
new FileInformation() { Title = "File 4", IsPlaying = false, Time = new TimeSpan(0, 5, 4) },
new FileInformation() { Title = "File 5", IsPlaying = false, Time = new TimeSpan(0, 5, 5) },
new FileInformation() { Title = "File 6", IsPlaying = false, Time = new TimeSpan(0, 5, 6) },
new FileInformation() { Title = "File 7", IsPlaying = false, Time = new TimeSpan(0, 5, 7) },
new FileInformation() { Title = "File 8", IsPlaying = false, Time = new TimeSpan(0, 5, 8) },
new FileInformation() { Title = "File 9", IsPlaying = false, Time = new TimeSpan(0, 5, 9) },
};
}
public void PlayToggle(int idx)
{
FilesCollection[idx].IsPlaying = !FilesCollection[idx].IsPlaying;
}
}
public class FileInformation : DependencyObject
{
public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register(nameof(IsPlaying), typeof(bool), typeof(FileInformation), new PropertyMetadata(null));
//private bool _isPlaying = false;
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set
{
SetValue(IsPlayingProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(FileInformation), new PropertyMetadata(null));
public string Title
{
get => (string)GetValue(TitleProperty);
set
{
SetValue(TitleProperty, value);
OnPropertyChanged();
}
}
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set { SetValue(TimeProperty, value); }
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof(TimeSpan), typeof(FileInformation), new PropertyMetadata(null));
public event PropertyChangedEventHandler PropertyChanged;
//[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value = IsPlaying
if (value is bool b)
{
return b;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value; // Needed because IsPlaying is twoway bound to IsSelected.
}
}
public class TimeSpanFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value = Time
if (value is TimeSpan ts)
{
return $"{ts.Minutes} minutes {ts.Seconds} seconds";
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
If you do not want IsSelected tied to IsPlaying but still want IsPlaying to trigger the yellow background then try this:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsPlaying}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>

Can you bind a complex type in a DataGridComboBoxColumn in a DataGrid in WPF?

So this one I am curious on as I may have to change my code base if I cannot get the data right. I was hoping a binding expert on WPF has had something similar and knew how to do it. I was following this guide, http://wpfthoughts.blogspot.com/2015/04/cannot-find-governing-frameworkelement.html, for binding a value in a list that is shown in datagrid to a combobox. Works great, if your property in the collection of objects is a primitive type. If it is complex not so much. I also want it to update the property when it changes implementing INotifyPropertyChanged.
Feel free to download the source code for easier reference: https://github.com/djangojazz/ComboBoxInDataGridViewWPF
BaseViewModel(just for INotifyPropertyChanged reuse):
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Essentially I have models as such:
public class Type
{
public Type(int typeId, string typeName)
{
TypeId = typeId;
TypeName = typeName;
}
public int TypeId { get; set; }
public string TypeName { get; set; }
}
public class TransactionSimple : BaseViewModel
{
public TransactionSimple(int transactionId, string description, int typeId, decimal amount)
{
TransactionId = transactionId;
Description = description;
TypeId = typeId;
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private int _typeId;
public int TypeId
{
get { return _typeId; }
set
{
_typeId = value;
OnPropertyChanged(nameof(TypeId));
}
}
public decimal Amount { get; set; }
}
public class TransactionComplex : BaseViewModel
{
public TransactionComplex(int transactionId, string description, int typeId, string typeName, decimal amount)
{
TransactionId = transactionId;
Description = description;
Type = new Type(typeId, typeName);
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private Type _type;
public Type Type
{
get { return _type; }
set
{
if(_type != null) { MessageBox.Show($"Change to {value.TypeName}"); }
_type = value;
OnPropertyChanged(nameof(Type));
}
}
public decimal Amount { get; set; }
}
And the ViewModel:
public sealed class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<TransactionSimple> _simples;
private ObservableCollection<TransactionComplex> _complexes;
public MainWindowViewModel()
{
FakeRepo();
}
private ReadOnlyCollection<Type> _types;
public ReadOnlyCollection<Type> Types
{
get => (_types != null) ? _types : _types = new ReadOnlyCollection<Type>(new List<Type> { new Type(1, "Credit"), new Type(2, "Debit") });
}
public ObservableCollection<TransactionSimple> Simples
{
get { return _simples; }
set
{
_simples = value;
OnPropertyChanged(nameof(Simples));
}
}
public ObservableCollection<TransactionComplex> Complexes
{
get { return _complexes; }
set
{
_complexes = value;
OnPropertyChanged(nameof(Complexes));
}
}
private void FakeRepo()
{
var data = new List<TransactionComplex>
{
new TransactionComplex(1, "Got some money", 1, "Credit", 1000m),
new TransactionComplex(2, "spent some money", 2, "Debit", 100m),
new TransactionComplex(3, "spent some more money", 2, "Debit", 300m)
};
Complexes = new ObservableCollection<TransactionComplex>(data);
Simples = new ObservableCollection<TransactionSimple>(data.Select(x => new TransactionSimple(x.TransactionId, x.Description, x.Type.TypeId, x.Amount)));
}
}
UPDATED 2:24 PM PST USA: And finally the view(almost working):
<Window x:Class="ComboBoxInDataGridViewWPF.MainWindow"
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"
xmlns:local="clr-namespace:ComboBoxInDataGridViewWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="Types" Source="{Binding Types}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SimpleExample" />
<DataGrid Grid.Row="1" ItemsSource="{Binding Simples}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<DataGridComboBoxColumn Header="Type" ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedValuePath="TypeId" SelectedValueBinding="{Binding Path=TypeId}" />
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
<Border Grid.Row="2" Height="50" Background="Black" />
<Label Content="ComplexObjectExample" Grid.Row="3" />
<DataGrid Grid.Row="4" ItemsSource="{Binding Complexes}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<!--This one works for the displays but not for the updates
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedItem="{Binding Type, Mode=TwoWay}" SelectedValue="{Binding Type.TypeId}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type.TypeName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This one works but the initial displays are wrong. This seems to be the closest to what I want-->
<DataGridComboBoxColumn Header="Type" SelectedItemBinding="{Binding Type}" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--This one does not work at all
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName" SelectedItem="{Binding Type}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The problem is shown like this:
I can obviously get the items bound to the ComboBox and I have seen by adding Observable Collections(not shown) and raising properties that the complex type is getting called. But it will not display no matter what I try. Trying the property of the property like Type.TypeName or such with different combinations doesn't work. Any ideas?
This ridiculous behaviour is well known. Because DataGridColumn lies not in the visual tree, the classic way using the DataGridComboBoxColumn to bind the items from parent like you tried is not working.
Instead you could create DataGridTemplateColumn with a ComboBox inside. This should solve your problem nearly in the same way. If you want to bind the TypeId this code works:
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedValuePath="TypeId"
SelectedValue="{Binding Path=TypeId, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Binding the whole Type could be done by changing the ComboBox to:
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged}"/>
Alternatively you can have a look at this question where other possible solutions are described.

I can't hide the window in MVVM

I have the startup window in a WPF application, and I have this code in my view:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}"
Visibility="{Binding EsUpdaterVisible, Mode=TwoWay}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Recursos/Diccionarios/Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Button Content="Aceptar" HorizontalAlignment="Left" Margin="10,145,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<cmd:EventToCommand Command="{Binding AceptarCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
My ViewModel:
private RelayCommand _aceptarCommand;
public RelayCommand AceptarCommand
{
get { return _aceptarCommand ?? (_aceptarCommand = new RelayCommand(aceptarCommand)); }
}
private void aceptarCommand()
{
try
{
EsUpdaterVisible = false;
Titulo = "Después de aceptar.";
}
catch { throw; }
}
private bool _esUpdaterVisible = true;
public bool EsUpdaterVisible
{
get { return _esUpdaterVisible; }
set
{
if (_esUpdaterVisible != value)
{
_esUpdaterVisible = value;
base.RaisePropertyChangedEvent("EsUpdaterVisible");
}
}
}
private string _titulo = "Inicio";
public string Titulo
{
get { return _titulo; }
set
{
if (_titulo != value)
{
_titulo = value;
base.RaisePropertyChangedEvent("Titulo");
}
}
}
When I click the aceptar button, the title of the window is changed, but the windows is still visible.
I would like to hide the window in some cases from the view model. How I could do that?
Thanks.
If you wouldn't like to use converter, just xaml part:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding EsUpdaterVisible,UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding EsUpdaterVisible,UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/> <!-- use hide instead of collapsed if you would like to open again this instance of window after close. -->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
Visibility is not a boolean type.
You can use a converter to accomplish that.
Converter:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Than your XAML will look something like this:
<Window x:Class="MyView"
Name="ucPrincipal"
Title="{Binding Titulo}"
Visibility="{Binding EsUpdaterVisible, Converter={StaticResource visibilityConverter}}">

Change style based on data

Consider below XAML =>
<fluent:Ribbon x:Name="MenuRibbon"
Title="title"
SnapsToDevicePixels="True">
<fluent:RibbonTabItem x:Name="Home"
Header="Home">
<fluent:RibbonGroupBox Header="Project">
<fluent:InRibbonGallery MinItemsInRow="3"
MaxItemsInRow="6"
Width="300"
ItemWidth="64"
ItemHeight="56"
ItemsSource="{Binding Projects}">
<fluent:InRibbonGallery.ItemTemplate>
<DataTemplate>
<DockPanel>
<Border BorderBrush="{x:Null}" Height="56">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" DockPanel.Dock="Bottom"
Text="{Binding Name}">
</TextBlock>
</Border>
</DockPanel>
</DataTemplate>
</fluent:InRibbonGallery.ItemTemplate>
</fluent:InRibbonGallery>
</fluent:RibbonGroupBox>
</fluent:RibbonTabItem>
</fluent:Ribbon>
I've bind ObservableColleciton of Projects to InRibbonGallery , And there is an instance of project (ActiveProject) exist in ViewModel.
a TextBlock defined in DataTemplate to display Name of Project object.
How can I change color of TextBlock that contains Active Project ?
ViewModel :
public class ViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ViewModel()
{
Projects = new ObservableCollection<Project>(new List<Project>
{
new Project {Id = "0", Name = "Project1"},
new Project {Id = "1", Name = "Project2"},
new Project {Id = "2", Name = "Project3"}
});
Project = Projects[0];
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
_projects = value;
RaisePropertyChanged(() => Projects);
}
}
public Project Project { get; set; }
}
public class Project : ObservableObject
{
private string _id;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Project files located here .
You can do this by using a MultiBinding and a DataTrigger in combination for a better result.
so your xaml would look like:
<Window.Resources>
<vm:ViewModel x:Key="ViewModel" />
<vm:ActiveProjectCheckConverter x:Key="ActiveProjectCheckConverter" />
</Window.Resources>
...
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ActiveProjectCheckConverter}">
<Binding Path="Name" />
<Binding Path="DataContext.ActiveProject.Name"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type fluent:InRibbonGallery}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="1,0">
<GradientStop Offset="0.0"
Color="#00FFFFFF" />
<GradientStop Offset="1.1"
Color="#FFFFFFFF" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
and your converter:
public class ActiveProjectCheckConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
string first = values[0] as string;
string second = values[1] as string;
return !string.IsNullOrEmpty(first) && !string.IsNullOrEmpty(second) && first == second;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Now I did make one more change, your Project object in your ViewModel, if you want changes to that to reflect in the View you need to make that implement INPC itself. So I did update that and also renamed it to ActiveProject
private Project _activeProject;
public Project ActiveProject {
get {
return _activeProject;
}
set {
if (value == _activeProject)
return;
_activeProject = value;
RaisePropertyChanged(() => ActiveProject);
}
}
Update
You can find the above updates at: Dropbox-Link

Categories

Resources