Make first row of DataGrid ReadOnly - wpf

I'm using a DataGrid to display user permissions in my WPF-application.
The first row of the DataGrid will always contain the owner of the project for which the permissions are displayed.
This owner is set when the project is created and can not be changed directly from the DataGrid.
My question then is.
How can I make the first row ReadOnly, and maybe give it a specific style so the background can be changed?

You will need a trigger for this, the only problem is that getting a row index on the wpf datagrid is pretty awful, so i tend to do something like this:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsOwner}" Value="true">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
where there's a property value on the object i know will be unique to that particular row. So if there's a unique ID or something that the owner always has, you can bind to that.
The setter property is 'IsEnabled' as datagridrow doesn't contain a property for readonly, but this will stop a user modifying the row.

Here's my take on it. The previous sample will do you just fine, my sample is for demonstration of the approach for acquiring low level control over the cells look. In WPF DataGridRow is just a logical container, you can only use 'attached' properties with it, such as Enabled, FontSize, FontWeight etc., as they'll get propagated down to the cell level), but the actual control's look is defined at a cell level.
TextBlock's for readonly stuff generally look cleaner than disabled texboxes, also you might want to apply completely different style for readonly and editable modes of your cells, for which you'll have to do somewhat similar to what the code below does.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ReadOnlyRows
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.g.ItemsSource = new List<Person>(2)
{
new Person(){ Name="Dmitry", Role="Owner" },
new Person(){ Name="Jody", Role="BA" }
};
};
}
}
public class Person
{
public string Role
{
get;
set;
}
public string Name
{
get;
set;
}
}
public class PersonServices
{
// that shouldn't be in template selector, whould it?
public static bool CanEdit(Person person)
{
return person.Role != "Owner";
}
}
public class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableDataTemplate" : "ReadOnlyDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
public class EditingTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableEditingDataTemplate" : "ReadOnlyEditingDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
}
XAML:
<Window x:Class="ReadOnlyRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ReadOnlyRows"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Resources>
<DataTemplate x:Key="EditableEditingDataTemplate">
<TextBox Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyEditingDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<DataTemplate x:Key="EditableDataTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector" />
<local:EditingTemplateSelector x:Key="EditingTemplateSelector" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name"
CellTemplateSelector="{StaticResource TemplateSelector}"
CellEditingTemplateSelector="{StaticResource EditingTemplateSelector}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

Related

WPF: How to bind object to ComboBox

Trying to learn how to bind objects to various types of controls. In this instance, I want to get sample data in my object to appear in ComboBox. The code runs but what appears instead of values (David, Helen, Joe) is text "TheProtect.UserControls.Client")
XAML: (ucDataBindingObject.xaml)
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ComboBox Width="310"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
this.DataContext = this;
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
There are several ways to tell the framework what to display
1) Use DisplayMemberPath on the ComboBox (this will display the named property):
<ComboBox ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name"
/>
2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display, rather than just a property:
<ComboBox ItemsSource="{Binding Path=Clients}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
3) Add a ToString() override to source class. Useful if you always want to display the same string for a given class. (Note that the default ToString() is just the class type name, which is why you see "TheProtect.UserControls.Client".)
public class Client
{
// ...
public override string ToString()
{
return string.Format("{0} ({1})", Name, ID);
}
}
4) Add a DataTemplate to the XAML resources. This is useful for associating a given class type with a more complex or stylized template.
<UserControl xmlns:local="clr-namespace:TheProject.UserControls">
<UserControl.Resources>
<DataTemplate DataType="local:Client">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
// ...
</UserControl>
In DisplayMemberPath, give the name of the property which you want to show in the comboBox. In SelectedValuePath, give the name of the property which you want to select. When you do a ComboBox.SelectedValue, you will get the value of this property.
Trying to get selected value from combobox returns System.Data.Entity.DynamicProxies.x
private void Button_Click(object sender, RoutedEventArgs e){
string _scanner0 = int.Parse(mycmb.SelectedValue.ToString());
string _scanner1 = mycbr.SelectedItem.ToString();
string _scanner2 = mycbr.SelectedValuePath.ToString();
string _scanner3 = mycbr.text.ToString();
}
all these Returns System.Data.Entity.DynamicProxies.x
What should i do?

How do I disable some controls based on validation in WPF?

I have a WPF application which consists of a TabControl. I have made a simple version of it for the purpose of this question:
In the first tab I have combobox which fills a datagrid. If I selected a row in the datagrid it gets bound a couple of textboxes and the user may edit its contents.
My objects in the datagrid implements the IDataErrorInfo interface and my textboxes has ValidatesOnDataErrors=True set in the {binding}. So if I erase the contents of the Name textbox it gets invalid (after the textbox loses focus):
Now, if it is invalid I don't want the user to be able to select another row in the datagrid, or select another row in the combobox (which would repopulate the datagrid). Basically I want the user to correct the name before he/she continues. Although, I would prefer if the user could switch tab.
So I either need to disable the controls to the left if the bound object is invalid or I need to set focus to the invalid textbox if I click on the controls to the left. I havn't found any suitable events or bindings for this. All ideas are appreciated.
Here is my XAML:
<Window x:Class="WpfValidationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350">
<TabControl>
<TabItem Header="Tab 1">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<ComboBox>
<ComboBox.Items>
<ComboBoxItem Content="Friends"/>
<ComboBoxItem Content="Business"/>
</ComboBox.Items>
</ComboBox>
<DataGrid Name="dg" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Address" Binding="{Binding Address}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical" Width="200" Margin="10,0,0,0">
<TextBlock Text="Edit" FontWeight="Bold"/>
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Path=SelectedItem.Name, ElementName=dg, ValidatesOnDataErrors=True}" />
<TextBlock Text="Address:"/>
<TextBox Text="{Binding Path=SelectedItem.Address, ElementName=dg, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="The user should be able to navigate to this tab even if there are validation errors" TextWrapping="Wrap" />
</TabItem>
</TabControl>
</Window>
And here is the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfValidationTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Person> persons = new List<Person>()
{
new Person(){Name="John Doe", Address="My street 203"},
new Person(){Name="Jane Doe", Address="Your street 43"}
};
dg.ItemsSource = persons;
}
}
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Name":
if (string.IsNullOrEmpty(Name))
return "Name must be entered";
break;
case "Address":
if (string.IsNullOrEmpty(Address))
return "Address must be entered";
break;
}
return null;
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
NotifyPropertyChanged("Address");
}
}
private void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
You can use a trigger to disable the control
<Style x:Key="disableOnValidation"
BasedOn="{StaticResource {x:Type DataGrid}}"
TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameTextBox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=addressTextbox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>

How to set a style for GridView cells

I'm puzzled how to simply right-align some columns in a GridView without writing tons of markup for every column.
I can't use a static CellTemplate, because cell templates are ignored when you set DisplayMemberBinding at the same time (see remarks in MSDN). Without DisplayMemberBinding, I would be back at one custom cell template per column because the binding is different, and that's what I want to avoid.
So a style would be great like we can use for the header:
<GridViewColumn DisplayMemberBinding="{Binding Bla}" HeaderContainerStyle="{StaticResource RightAlignStyle}" />
However, I can't find a property to set a style for cell items.
Probably I'm missing the forest through the trees...
Markus, here's what I would do. Bite the bullet and for the price of writing 10 lines of code get yourself a first class support for alignments and any other unsupported properties. you can traverse the visual tree and look up for PART_* thing for the heavy fine tunung.
The solution is:
1. Alignable Column Class:
namespace AlignableCellsProject
{
public class AlignableTextColumn: DataGridTextColumn
{
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
FrameworkElement element = base.GenerateElement(cell, dataItem);
element.SetValue(FrameworkElement.HorizontalAlignmentProperty, this.HorizontalAlignment);
return element;
}
protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
FrameworkElement element = base.GenerateEditingElement(cell, dataItem);
element.SetValue(FrameworkElement.HorizontalAlignmentProperty, this.HorizontalAlignment);
return element;
}
public HorizontalAlignment HorizontalAlignment
{
get { return (HorizontalAlignment)this.GetValue(FrameworkElement.HorizontalAlignmentProperty); }
set { this.SetValue(FrameworkElement.HorizontalAlignmentProperty, value); }
}
}
}
2. Consumer's XAML:
<Window x:Class="AlignableCellsProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AlignableCellsProject"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g" AutoGenerateColumns="False">
<DataGrid.Columns>
<local:AlignableTextColumn HorizontalAlignment="Left"
Width="200" Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
3 - Consumer's Code Behind:
namespace AlignableCellsProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded +=
(o, e) =>
{
this.g.ItemsSource = Enumerable.Range(1, 3);
};
}
}
}
I am not using .Net 4.0 but this serves the purpose...
<tk:DataGrid ItemsSource="{Binding}" IsReadOnly="True" AutoGenerateColumns="True">
<tk:DataGrid.Resources>
<Style x:Key="MyAlignedColumn" TargetType="{x:Type tk:DataGridCell}">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
</tk:DataGrid.Resources>
<tk:DataGridTextColumn Header="Name"
CellStyle="{StaticResource MyAlignedColumn}"
Binding="{Binding Name, Mode=TwoWay}"/>
</tk:DataGrid>

How do I bind to something outside a datacontext

I have a listbox in WPF that is in the Layout Root.
I also have a Frame that is in the Layout Root as well.
The listbox is composed of items that have a string(Name) and a framework element(UI).
How do I bind the frame's content to be the UI property of the listbox's selected item property?
If you need a codebehind, how would you do this in MVVM
I used a ContentControl instead of Frame since I had problem binding to Content property, I never got it to refresh after binding changed. I didn't do proper MVVM, Data should not be hosted inside the view.
XAML:
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding}" />
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource CVS}}">
<ListBox
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name">
</ListBox>
<ContentControl Content="{Binding Path=UI}" />
</StackPanel>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace BindDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Data = new List<DataItem>();
Data.Add(new DataItem("TextBox", new TextBox(){ Text="hello" }));
Data.Add(new DataItem("ComboBox", new ComboBox()));
Data.Add(new DataItem("Slider", new Slider()));
DataContext = Data;
}
public List<DataItem> Data
{
get; private set;
}
}
public class DataItem
{
public DataItem(string name, FrameworkElement ui)
{
Name = name;
UI = ui;
}
public string Name { get; private set; }
public FrameworkElement UI { get; private set; }
}
}
It sounds as you want to display list of objects and details for selected object. If I am right, solution in MVVM may be following:
<ListView ItemsSource="{Binding ObjectsList}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding ObjectsList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<!-- details template -->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

wpf treeview mvvm

I am trying to populate a treeview using mvvm but the tree does not display any data.
I have a Employee list which is a property in my vm which contains he employee data.
the xaml is as follows.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding EmpList}" >
<TextBlock Text="{Binding EmpName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Is there anything i am missing here.
thanks
Hi Ian's suggested article indeed is a great read!
The trick is that you should specify how the Treeview shows its items through type specific (Hierarchical)DataTemplates. You specify these datatemplates in the Treeview's resources (or higher up the visual tree if you want to reuse them in more treeviews).
I tried to simulate what you want:
<Window x:Class="TreeViewSelection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewSelection"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TreeView ItemsSource="{Binding Enterprises}">
<TreeView.Resources>
<!-- template for showing the Enterprise's properties
the ItemsSource specifies what the next nested level's
datasource is -->
<HierarchicalDataTemplate DataType="{x:Type local:Enterprise}"
ItemsSource="{Binding EmpList}">
<Label Content="{Binding EntName}"/>
</HierarchicalDataTemplate>
<!-- the template for showing the Employee's properties-->
<DataTemplate DataType="{x:Type local:Employee}">
<Label Content="{Binding EmpName}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TreeViewSelection
{
public partial class Window1 : Window
{
public ObservableCollection<Enterprise> Enterprises { get; set; }
public Window1()
{
InitializeComponent();
Enterprises = new ObservableCollection<Enterprise>
{
new Enterprise("Sweets4Free"),
new Enterprise("Tires4Ever")
};
DataContext = this;
}
}
public class Enterprise : DependencyObject
{
public string EntName { get; set; }
public ObservableCollection<Employee> EmpList { get; set; }
public Enterprise(string name)
{
EntName = name;
EmpList = new ObservableCollection<Employee>
{
new Employee("John Doe"),
new Employee("Sylvia Smith")
};
}
}
public class Employee : DependencyObject
{
public string EmpName { get; set; }
public Employee(string name)
{
EmpName = name;
}
}
}
Check out Josh Smith's article on exactly this topic... Helped me no end!
p.s. Looks like you're missing the DataType property on the HierarchicalDataTemplate, e.g.
<HierarchicalDataTemplate
DataType="{x:Type local:ItemType}"
ItemsSource="{Binding ...}" >

Resources