How to clean the previously selected row ?
I have this method that search for item with a given id.
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
foreach (MessageFieldViewModel rowItem in Datagrid.ItemsSource)
{
if (_mainWindowModel != null)
_mainWindowModel.SelectedMessageElement = (MessageElementViewModel)e.NewValue;
var row = Datagrid.ItemContainerGenerator.ContainerFromItem(rowItem) as DataGridRow;
if (_mainWindowModel != null && _mainWindowModel.SelectedMessageElement != null)
{
if (rowItem.Id == _mainWindowModel.SelectedMessageElement.Id)
{
if (row != null)
row.Background = Brushes.DarkSalmon;
}
row.Background.ClearValue();
}
// if (item != null) row.Background.ClearValue(rowItem.Id);
// break;
}
This selects the row of a given id. but if I want to select another Id The previous id is still selected.
how can I remove previously selected Ids and show only the newly selected id?
ok so here we go
XAML
<TreeView Name="Tree" Width="50" Height="100"
ItemsSource="{Binding YourTree}"
SelectedItemChanged="Tree_SelectedItemChanged"
SelectedValuePath="yourIdProperty"/>
<DataGrid Name="myDatagrid" Width="100" Height="100"
ItemsSource="{Binding YourList}"
SelectedItem="{Binding YourSelectedItem,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Id"/>
Codebehind
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var tree = sender as TreeView;
myDatagrid.SelectedValue = tree.SelectedValue;
}
ViewModel
public List<yourClass> YourList { get; set; }
private yourClass yourSelectedItem;
public yourClass YourSelectedItem
{
get { return yourSelectedItem; }
set
{
yourSelectedItem = value;
OnPropertyChanged("YourSelectedItem");
}
}
you need to set the SelectedValuePath based your Property which represent your Id but not as Binding just as string as example "Id"
Related
I am building a panorama which displays images through binding. I need to find index of panorama item whenever the current item changes. But the SELECTIONCHANGED event is not firing in case when data is retrieved through binding. Can you please suggest some other way. Thanx in advance
XAML Code
<phone:Panorama x:Name="HeaderPanorama"
ItemsSource="{Binding PanoramaImages}"
Width="550" Margin="-10,-255,0,-140"
SelectionChanged="HeaderPanorama_SelectionChanged_1">
<phone:Panorama.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Margin="-10"/>
</DataTemplate>
</phone:Panorama.ItemTemplate>
</phone:Panorama>
CodeBehind
private void HeaderPanorama_SelectionChanged_1(
object sender,
SelectionChangedEventArgs e)
{
if (this.DataContext != null && this.DataContext is HomeViewModel)
{
((HomeViewModel)this.DataContext).PanoramaItemIndex =
HeaderPanorama.SelectedIndex;
}
}
ViewModel Code
public HomeViewModel()
{
RequestHomeData();
PanoramaImages = new List<string>();
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
}
private List<string> _panoramaImages;
public List<string> PanoramaImages
{
get { return _panoramaImages; }
set
{
_panoramaImages = value;
NotifyPropertyChanged("PanoramaImages");
}
}
private int _panoramaItemIndex;
public int PanoramaItemIndex
{
get { return _panoramaItemIndex; }
set
{
_panoramaItemIndex = value;
NotifyPropertyChanged("PanoramaItemIndex");
}
}
How do you make a DataGrid always keeping the last row visible?
As in automatically scrolling to the bottom when new items are added
Yes, you can use method ScrollIntoView a pass DataGrid item to this method.
XAML:
<DataGrid x:Name="DataGrid" Grid.Row="1"
Margin="5"
AutoGenerateColumns="True"
ItemsSource="{Binding Path=Users}">
Code:
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get
{
return _users;
}
set
{
_users = value;
OnPropertyChanged("Users");
}
}
Add new item to DataGrid:
private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
{
Users.Add(new User { Id = Guid.NewGuid().ToString(), FirstName = "Bill", LastName = "Clinton" });
//scroll to last added item
DataGrid.ScrollIntoView(Users[Users.Count-1]);
}
This is a simple approach using LoadingRow event:
void dataGrid_LoadingRow(object sender, System.Windows.Controls.DataGridRowEventArgs e)
{
dataGrid.ScrollIntoView(e.Row.Item);
}
Just remember to disable it after grid loading is finished.
Simplest way:
DataGrid.SelectedIndex = DataGrid.Items.Count - 1; // If you need to select the last item
DataGrid.ScrollIntoView(DataGrid.Items[DataGrid.Items.Count - 1]);
After already try many way,this is the best way:
if (datagrid.Items.Count > 0)
{
var border = VisualTreeHelper.GetChild(datagrid, 0) as Decorator;
if (border != null)
{
var scroll = border.Child as ScrollViewer;
if (scroll != null) scroll.ScrollToEnd();
}
}
I have bound the DataTable to the DataGrid control. How can I set the selected item programmatically ?
Example
In my view model I have a property of type DataTable to bind the DataGrid
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
My XAML
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,120" />
The constructor of the view model (assigning dummy values)
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
this.SizeQuantityTable.Columns.Add(sizeQuantityColumn);
DataColumn sColumn = new DataColumn();
sColumn.ColumnName = "S";
this.SizeQuantityTable.Columns.Add(sColumn);
DataColumn mColumn = new DataColumn();
mColumn.ColumnName = "M";
this.SizeQuantityTable.Columns.Add(mColumn);
DataRow row1 = this.SizeQuantityTable.NewRow();
row1[sizeQuantityColumn] = "Blue";
row1[sColumn] = "12";
row1[mColumn] = "15";
this.SizeQuantityTable.Rows.Add(row1);
DataRow row2 = this.SizeQuantityTable.NewRow();
row2[sizeQuantityColumn] = "Red";
row2[sColumn] = "18";
row2[mColumn] = "21";
this.SizeQuantityTable.Rows.Add(row2);
DataRow row3 = this.SizeQuantityTable.NewRow();
row3[sizeQuantityColumn] = "Green";
row3[sColumn] = "24";
row3[mColumn] = "27";
this.SizeQuantityTable.Rows.Add(row3);
OK. I have created three columns namely sizeQuantityColumn, sColumn and mColumn and added three rows namely row1, row2 and row2.
So, Let's say I wanna set the selected item as row2 (So in the view, the second row should be highlighted).
How can I do this?
EDIT
I hardcoded the SelectedIndex of the DataGrid to 1. (So the second row should be selected). In design time it shows as selected. But not in the run time. You can see it in the below snapshot.
So ultimaltely the problem is Not highlighting the row.
There are a few way to select items in the DataGrid. It just depends which one works best for the situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);
The most common one is SelectedValue with SelectedValuePath set, in this case you set the column you want to select with and then to can select the row by setting the corresponding value
<DataGrid SelectedValuePath="Size Quantity" SelectedValue="{Binding SelectionValue}"
private string _selectedValue
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
SelectionValue = "Blue";
Edit:
Here is my test and it is highlighting just fine
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
...................
........
}
private string _selectedValue;
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get { return sizeQuantityTable; }
set { sizeQuantityTable = value; NotifyPropertyChanged("SizeQuantityTable"); }
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectedIndex = 2;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
SelectionValue = "Blue";
}
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="202" Width="232" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<DataGrid SelectedValuePath="Size Quantity"
SelectedValue="{Binding SelectionValue}"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,41" />
<StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
<Button Content="SelectedIndex" Height="26" Width="107" Click="Button_Click_1"/>
<Button Content="SelectedValue" Height="26" Width="107" Click="Button_Click_2"/>
</StackPanel>
</Grid>
</Window>
Result:
You can always use SelectedItem property and bind it against row, as such:
SelectedItem="{Binding ActiveRow}"
and in ViewModel do:
ActiveRow = secondRow;
Add SelectedItem, SelectedValue in your DataGrid.
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
SelectedValue ="{Binding SelectedValue}"
Margin="0,0,0,120" />
And in your view model
private string _selectedValue;
public string SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
NotifyPropertyChanged("SelectedValue");
}
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
You can use SelectedItem as well, that is preferred.
I just had the same problem.
I saw that the item of a datagrid was selected correctly at design time, but not at runtime. (By the way, I create the instance of the view model in the xaml).
I could solve this problem by moving the code to programmatically set the selected item from the view models constructor to a different method in the view model and then calling this method in the loaded event of the window (or usercontrol).
Obviously the view is not completely done initializing itself when the view models constructor is called.
This can be avoided by not coding in the view models constructor.
View (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels" Loaded="Window_Loaded">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
View (code behind):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ExampleViewModel)this.DataContext).LoadData();
}
If you do not like setting up the Loaded event in the code behind, you can also do it in xaml (references to "Microsoft.Expression.Interactions" and "System.Windows.Interactivity" are needed):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In each case, you call the LoadData method in the ViewModel:
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT set selected item here
}
public void LoadData()
{
// Set selected item here
}
For anyone using Observable Collections you may find this solution useful.
The SelectedModelIndex property simply returns the index of the SelectedModel from the ItemSource collection. I've found setting the SelectedIndex along with the SelectedItem highlights the row in the DataGrid.
private ObservableCollection<Model> _itemSource
public ObservableCollection<Model> ItemSource
{
get { return _itemSource; }
set
{
_itemSource = value;
OnPropertyChanged("ItemSource");
}
}
// Binding must be set to One-Way for read-only properties
public int SelectedModelIndex
{
get
{
if (ItemSource != null && ItemSource.Count > 0)
return ItemSource.IndexOf(SelectedModel);
else
return -1;
}
}
private Model _selectedModel;
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
OnPropertyChanged("SelectedModelIndex");
}
}
xaml:
<DataGrid SelectionUnit="FullRow" >
</DataGrid>
code
SelectRowByIndex(yourDataGridViewName, 0);
public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
{
if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");
if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));
dataGrid.SelectedItems.Clear();
/* set the SelectedItem property */
object item = dataGrid.Items[rowIndex]; // = Product X
dataGrid.SelectedItem = item;
DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
if (row == null)
{
/* bring the data item (Product object) into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(item);
row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
}
//TODO: Retrieve and focus a DataGridCell object
if (row != null)
{
DataGridCell cell = GetCell(dataGrid, row, 0);
if (cell != null)
cell.Focus();
}
}
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
/* if the row has been virtualized away, call its ApplyTemplate() method
* to build its visual tree in order for the DataGridCellsPresenter
* and the DataGridCells to be created */
rowContainer.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
/* bring the column into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
}
return null;
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in FindVisualChildren<childItem>(obj))
{
return child;
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
I've got a list of items bound to a ComboBox. When a user selects an item, I'd like to cancel the selection and select a different item instead. This must happen from within the setter of the property that the SelectedItem is bound to. I'm using Silverlight 3.
My data model for each item in the ComboBox:
public class DataItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Object that is set to the DataContext:
public class DataContainer : INotifyPropertyChanged
{
public DataContainer()
{
itemList = new List<DataItem>();
itemList.Add(new DataItem() { Id = 1, Name = "First" });
itemList.Add(new DataItem() { Id = 2, Name = "Second" });
itemList.Add(new DataItem() { Id = 3, Name = "Third" });
}
public event PropertyChangedEventHandler PropertyChanged;
private DataItem selectedItem;
public DataItem SelectedItem
{
get { return selectedItem; }
set
{
if (value != null && value.Id == 2)
value = itemList[0];
selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private List<DataItem> itemList;
public List<DataItem> ItemList
{
get { return itemList; }
set { itemList = value; NotifyPropertyChanged("DataList"); }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Relevant bits of xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox" DisplayMemberPath="Name" Width="100" ItemsSource="{Binding ItemList}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
<Button Content="Set to First" Width="100" Click="Button_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Selected item: "/>
<TextBlock Text="{Binding SelectedItem.Id}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding SelectedItem.Name}"/>
</StackPanel>
</StackPanel>
It looks like my code to select the first item when the user selects the second item is working. The selected item is in fact set to "First" while the ComboBox is still displaying "Second" as if it was selected.
Is there any way to force the ComboBox to redraw or to reconsider what it should visually mark as selected?
I do this from the above mentioned Button_Click method and it works:
private void Button_Click(object sender, RoutedEventArgs e)
{
var c = DataContext as DataContainer;
if (c != null)
{
c.SelectedItem = null;
c.SelectedItem = c.ItemList[0];
}
}
But setting to null and then the value I want doesn't work if I do it from within the setter like I need to.
I may have found a solution for you. I was able to get what I think you're asking for working by doing the following:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
if (value != null && value.Id == 2)
{
value = itemList[0];
UpdateUI(); // Call this to force the UI to update.
}
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private void UpdateUI()
{
ThreadPool.QueueUserWorkItem(
o =>
{
Thread.Sleep(1);
Deployment.Current.Dispatcher.BeginInvoke(()=>
{
_selectedItem = null;
NotifyPropertyChanged("SelectedItem");
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem");
});
});
}
I wish I could explain to you why this is working, but I can only guess. Basically, its exiting the UI thread, and then re-entering a moment later via the Dispatcher.BeginInvoke() call. This appears to give the ComboBox control time to update itself from the user interaction, and then respond to the Dispatcher execution.
One problem I've found is that Silverlight seems to go a little wonky after multiple executions of the threading code. Increasing the Thread.Sleep time seems to help. I think this solution will work for the majority of situations and won't be an issue.
You don't have to queue a Thread with 1 second waiting as grimus suggested. this should also work for you:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
if (value != null && value.Id == 2)
{
Diployment.Current.Dispatcher.BeginInvoke(() => {
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem"); });
}
}
}
I'm trying to implement column chooser functionality for a DataGrid and am running into problems if I try to define the content of the header for the column as something more than just a string. Below is a very simplified example with all styles, view models, binding, etc all stripped out.
There are 3 columns:
The first column uses a string for the header.
The second column tries to set the header content to a Label with a ToolTip.
The third column ties to set the header content to a TextBlock with a ToolTip.
Clicking the Toggle Visibility button for Column A works fine. The Toggle Visibility buttons for both Columns B and C cause an InvalidOperationException with the message "Specified element is already the logical child of another element. Disconnect it first."
<Window x:Class="DataGridColumnChoosing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Margin="15, 0">Toggle Visibility:</TextBlock>
<Button Click="ToggleA">Column A</Button>
<Button Click="ToggleB">Column B</Button>
<Button Click="ToggleC">Column C</Button>
</StackPanel>
<!-- Main Fuel Mileage Datagrid -->
<DataGrid x:Name="mySampleDataGrid" Grid.Row="1"
AutoGenerateColumns="False" CanUserSortColumns="False" CanUserResizeRows="False" CanUserAddRows="False"
GridLinesVisibility="All" RowHeaderWidth="0">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="colA" Width="40*" IsReadOnly="True" Header="Column A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C " />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
The simple click event handlers for the buttons that are toggling the visibility in this example are simply modifying the visibility of the columns.
private void ToggleA(object sender, RoutedEventArgs e)
{
colA.Visibility = colA.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleB(object sender, RoutedEventArgs e)
{
colB.Visibility = colB.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleC(object sender, RoutedEventArgs e)
{
colC.Visibility = colC.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
Thanks all.
I had this issue once when I had a control defined in my Resources, and was trying to use it within multiple control's Content areas. That does not work because the control can only belong to one parent.
Instead, I needed to define a Template of some kind which contained the control I wanted, and set the Template of my object instead of the content directly.
Your comment on #Gimno's answer makes me think this is the case.
Try changing it so instead of setting a Label/TextBox in DataGrid.Header's content directly, set DataGrid.HeaderTemplate to a DataTemplate which contains the Label or TextBox.
EDIT
Here's some example code
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I think it would be easiest if you just use DataGridTemplateColumn.HeaderStyle instead of DataGridTemplateColumn.Header
As example for column c:
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C "/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGridTemplateColumn.HeaderStyle>
I liked the column chooser solution from CodePlex:
DataGrid Behavior
I clean up the code and remove unnecessary code:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Xaml.Behaviors;
namespace Behaviors
{
public class WpfDataGridConfigurationBehavior : Behavior<DependencyObject>
{
#region "public Properties"
public bool DontPersistVisibleColumns { get; set; }
public bool DontPersistColumnsOrder { get; set; }
public string ContextMenuChoices { get; set; } = "Alphabetical Menu,Show All Columns";
public string DataGridName { get; set; }
#endregion "public Properties"
#region "private Properties"
private DataGrid dataGrid;
private ContextMenu theContextMenu; // Context Menu for the field chooser.
private string AllColumnsHeaders { get; set; }
private string AllColumnDisplayIndexes { get; set; }
private int nBaseItems = 5;
private MenuItem mnuAlpha;
private MenuItem mnuShowAll;
#endregion "Private Properties"
protected override void OnAttached()
{
base.OnAttached();
dataGrid = this.AssociatedObject as DataGrid;
if (DataGridName == null) DataGridName = dataGrid.Name;
ContextMenu_BuildStaticMenu();
dataGrid.Loaded += (sender, e) => { DataGrid_Loaded(); };
dataGrid.AutoGeneratedColumns += DataGrid_AutoGeneratedColumns;
dataGrid.ColumnReordered += (sender, e) => { DataGrid_ColumnReordered(sender); };
dataGrid.ContextMenuOpening += (sender, e) => { DataGrid_ContextMenuOpening(e); };
dataGrid.LoadingRow += (sender, e) => { e.Row.Header = (e.Row.GetIndex() + 1).ToString(); };
dataGrid.RowHeaderWidth = 0;
var ns = new Style(typeof(DataGridRowHeader)) { BasedOn = dataGrid.RowHeaderStyle };
ns.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.Bold));
ns.Setters.Add(new Setter(Control.FontSizeProperty, 11.0));
ns.Setters.Add(new Setter(Control.PaddingProperty, new Thickness(4, 0, 4, 0)));
dataGrid.RowHeaderStyle = ns;
}
private void DataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
DataGrid_Loaded();
}
#region "DataGrid Events"
private void DataGrid_ContextMenuOpening(ContextMenuEventArgs e)
{
var dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree
while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader))
dep = VisualTreeHelper.GetParent(dep);
if (dep == null)
return;
if (dep is DataGridColumnHeader)
{
// do something
}
if (dep is DataGridCell)
{
// navigate further up the tree
while ((dep != null) && !(dep is DataGridRow))
dep = VisualTreeHelper.GetParent(dep);
var row = dep as DataGridRow;
dataGrid.ItemContainerGenerator.IndexFromContainer(row);
}
}
private void DataGrid_ColumnReordered(object sender)
{
Settings_SaveDisplayIndexes(sender);
ContextMenu_BuildMenu();
}
private void DataGrid_Loaded()
{
ContextMenu_BuildMenu(false);
VisibleColumns_Initialize();
}
#endregion "DataGrid Events"
#region "ContextMenu Methods and Events"
private void ContextMenu_BuildStaticMenu()
{
theContextMenu = new ContextMenu { FontSize = 11, StaysOpen = true };
mnuAlpha = new MenuItem
{
Header = ContextMenuChoices.Split(',')[0],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuAlpha.Click += (sender, e) => { ContextMenu_BuildMenu(); };
mnuShowAll = new MenuItem
{
Header = ContextMenuChoices.Split(',')[1],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuShowAll.Checked += (sender, e) => { VisibleColumns = AllColumnsHeaders; };
mnuShowAll.Click += (sender, e) =>
{
if (mnuShowAll.IsChecked == false) VisibleColumns_Initialize();
};
theContextMenu.Items.Add(mnuShowAll);
theContextMenu.Items.Add(mnuAlpha);
}
private void ContextMenu_BuildMenu(bool pbRebuild = true)
{
for (int i = theContextMenu.Items.Count - 1; i > 0; i--)
if (((MenuItem)theContextMenu.Items[i]).FontWeight != FontWeights.Bold)
theContextMenu.Items.Remove(theContextMenu.Items[i]);
nBaseItems = theContextMenu.Items.Count;
// Attach the context menu to the DataGrid ColumnHeaders
var headersPresenter = WpfDataGridConfigurationBehaviorFinder.FindChild<DataGridColumnHeadersPresenter>(dataGrid);
ContextMenuService.SetContextMenu(headersPresenter, theContextMenu);
if (VisibleColumns == null)
throw (new SettingsPropertyNotFoundException("User's VisibleColumns setting not found."));
// Get the current column ordering from user.config
if (DisplayIndexes == null)
throw (new SettingsPropertyNotFoundException("User's DisplayIndexes setting not found."));
AllColumnDisplayIndexes = DisplayIndexes;
string[] colIndexes = AllColumnDisplayIndexes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var dataGridColumns = dataGrid.Columns.OrderBy(x => x.DisplayIndex);
// Sort the columns in display index order so menu header order matchs display column order
if (pbRebuild == false)
// Initially the datagrid column order is such that display indexes are the same as the col indexes
if (colIndexes.Length > 0)
dataGridColumns = dataGrid.Columns.OrderBy(x => Convert.ToInt16(colIndexes[x.DisplayIndex]));
if (mnuAlpha.IsChecked)
dataGridColumns = dataGrid.Columns.OrderBy(x => x.Header.ToString());
AllColumnsHeaders = "";
foreach (var col in dataGridColumns)
{
// All column name to a list of all column headers for later use.
AllColumnsHeaders = $"{col.Header.ToString().Replace("\n", " ").Replace("\r", " ")};{AllColumnsHeaders}";
// Add new menu item in display order.
ContextMenu_AddNewMenuItem(col);
}
string sTemp = VisibleColumns;
VisibleColumns = null;
VisibleColumns = sTemp;
}
private void ContextMenu_AddNewMenuItem(DataGridColumn col)
{
var menuItem = new MenuItem { Header = col.Header.ToString().Replace("\n", " ").Replace("\r", " "), StaysOpenOnClick = true };
var saVisibleColumns = new List<string> { string.Empty };
if (VisibleColumns != null)
{
saVisibleColumns = VisibleColumns.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
menuItem.IsChecked = (saVisibleColumns.Contains(menuItem.Header));
menuItem.Click += (sender, e) => { ContextMenu_ColumnName_Click(sender); };
theContextMenu.Items.Add(menuItem);
}
private void ContextMenu_ColumnName_Click(object sender)
{
var mi = sender as MenuItem;
// Get the column name that was clicked
string colName = mi.Header.ToString();
// Capture new visible columns list
Settings_SaveVisibleColumns(mi, colName);
}
#endregion "ContextMenu Methods and Events"
#region "Settings Methods"
private void Settings_SaveVisibleColumns(MenuItem mi, string colName)
{
if (theContextMenu.Items.Count - nBaseItems < dataGrid.Columns.Count)
return;
// Put the visible column names into an array
var saVisibleColumns = VisibleColumns.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (saVisibleColumns != null && saVisibleColumns.Count > 0 &&
saVisibleColumns[saVisibleColumns.Count - 1].StartsWith("-"))
saVisibleColumns.RemoveAt(saVisibleColumns.Count - 1);
// If the menu item is unchecked (column is not visible)
if (!mi.IsChecked)
// Make the column visible by adding its name to the Visible Columns list
saVisibleColumns.Add(colName);
else
// Hide the column by removing its name from the VisibleColumns list
if (saVisibleColumns.Contains(colName) && saVisibleColumns.Count > 1)
saVisibleColumns.Remove(colName);
VisibleColumns = string.Join(";", saVisibleColumns) + ";";
}
private void Settings_SaveDisplayIndexes(object sender)
{
// Capture the new column order
AllColumnDisplayIndexes = "";
foreach (DataGridColumn col in ((DataGrid)sender).Columns)
{
AllColumnDisplayIndexes +=
(AllColumnDisplayIndexes.Length > 0 ? ";" : "") + col.DisplayIndex;
}
DisplayIndexes = AllColumnDisplayIndexes;
}
#endregion "Settings Methods"
#region DisplayIndexes (DependencyProperty)
public string DisplayIndexes
{
get { return (string)GetValue(DisplayIndexesProperty); }
set { SetValue(DisplayIndexesProperty, value); }
}
public static readonly DependencyProperty DisplayIndexesProperty =
DependencyProperty.Register("DisplayIndexes", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnDisplayIndexesChanged));
private static void OnDisplayIndexesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).DisplayIndexesChanged(e);
}
public void DisplayIndexesChanged(DependencyPropertyChangedEventArgs e)
{
// Persist the new column order
if (dataGrid != null && !DontPersistColumnsOrder)
Settings_Save(DataGridName + "DisplayIndexes", AllColumnDisplayIndexes);
}
#endregion "DisplayIndexes (DependencyProperty)"
#region VisibleColumns (DependencyProperty)
/// <summary>
///
/// Gets or sets a value indicating the names of columns
/// (as they appear in the column header) to be visible, seperated by a semicolon.
///
/// Columns whose names are not here will be hidden.
/// </summary>
public string VisibleColumns
{
get { return (string)GetValue(VisibleColumnsProperty); }
set { SetValue(VisibleColumnsProperty, value); }
}
private void VisibleColumns_Initialize()
{
// Get saved VisibleColumns from app.config
// Initialize VisibleColumns
VisibleColumns = string.IsNullOrEmpty(VisibleColumns.Replace("\n", " ").Replace("\r", " ")) ? AllColumnsHeaders : VisibleColumns;
}
public static readonly DependencyProperty VisibleColumnsProperty =
DependencyProperty.Register("VisibleColumns", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnVisibleColumnsChanged));
private static void OnVisibleColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).VisibleColumnsChanged(e);
}
/// <summary>
///
/// Updates the display
///
/// </summary>
/// <param name="e"></param>
public void VisibleColumnsChanged(DependencyPropertyChangedEventArgs e)
{
if (theContextMenu == null)
return;
if (e.NewValue != null)
{
var showTheseColumns = e.NewValue.ToString().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var saContextMenuVisibleItems = new List<string>();
int iCol = 0;
foreach (MenuItem menuItem in theContextMenu.Items)
{
// Show/Hide the Context Menu item's checkmark.
if (menuItem.FontWeight == FontWeights.Bold) continue;
menuItem.IsChecked = showTheseColumns.Contains(menuItem.Header.ToString());
if (menuItem.IsChecked) saContextMenuVisibleItems.Add(menuItem.Header.ToString());
mnuShowAll.IsChecked = mnuShowAll.IsChecked && menuItem.IsChecked;
// Assign menu item's column's DisplayIndex in display order, (i.e. in menu item order), looking up each column by header name.)
if (mnuAlpha.IsChecked == false)
dataGrid.Columns.First(x => x.Header.ToString().Replace("\n", " ").Replace("\r", " ") == menuItem.Header.ToString()).DisplayIndex = iCol++;
}
// Show the columns
foreach (var col in dataGrid.Columns)
col.Visibility =
showTheseColumns.Contains(col.Header.ToString().Replace("\n", " ").Replace("\r", " "))
&& (saContextMenuVisibleItems.Contains(col.Header.ToString().Replace("\n", " ")
.Replace("\r", " ")))
? Visibility.Visible
: Visibility.Collapsed;
// Persist the new visible columns list
if (dataGrid != null && !DontPersistVisibleColumns)
Settings_Save(DataGridName + "VisibleColumns", VisibleColumns);
}
}
#endregion "VisibleColumns"
public static void Settings_Save(string propertyName, string propertyValue)
{
foreach (SettingsPropertyValue property in Settings.Default.PropertyValues)
{
if (propertyName == property.Name)
{
property.PropertyValue = propertyValue;
Settings.Default.Save();
}
}
}
}
public static class WpfDataGridConfigurationBehaviorFinder
{
public static T FindChild<T>(DependencyObject depObj) where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T) return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
T obj = FindChild<T>(VisualTreeHelper.GetChild(depObj, i));
if (obj != null) return obj;
}
return null;
}
public interface IBreakVisualParenting
{
DependencyObject Parent { get; }
}
public static T LastVisualAncestorOfType<T>(this DependencyObject element) where T : DependencyObject
{
T item = null;
var parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
if (parent is T)
item = (T)parent;
if (parent is IBreakVisualParenting)
{
parent = ((IBreakVisualParenting)parent).Parent;
}
else
parent = VisualTreeHelper.GetParent(parent);
}
return item;
}
}
}