I need to create tiles of well formatted buttons, something like the Windows 8 start page. Is there any toolkit available for a custom ListView which may support tile view or grid view, with some formatting and may be some animation options.
I tried creating my own custom listview but it seemed to be a complicated task.
I am not aware of a nice free tile control. DevExpress has a nice looking commercial version.
If you'd specify your exact requirements (i.e. what properties do you need configurable, what kind of animation,...) and I find the time, I would give it a whirl though.
EDIT: I've created an ItemsControl with a WrapPanel as ItemsPanel. Using the MVVM pattern, expanding the controls to your needs and your data objects should not be too difficult. Rather hard to do is the DragDrop behavior part - there certainly is still some room for improvement. I didn't include the images.
TileControl.xaml:
<UserControl x:Class="WpfApplication1.TileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<local:TileTypeToColorConverter x:Key="TileTypeToColorConverter" />
</UserControl.Resources>
<Grid>
<Image Source="/WpfApplication1;component/Themes/background.png" Stretch="UniformToFill" />
<Border x:Name="darkenBorder" Background="Black" Opacity="0.6" />
<ItemsControl ItemsSource="{Binding Tiles}" Background="Transparent" Margin="5">
<i:Interaction.Behaviors>
<beh:ItemsControlDragDropBehavior />
</i:Interaction.Behaviors>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:TileModel">
<Button Content="{Binding Text}" Background="{Binding TileType, Converter={StaticResource TileTypeToColorConverter}}"
Command="{Binding ClickCommand}" Width="120" Height="110" Padding="5" RenderTransformOrigin="0.5, 0.5" >
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Button.RenderTransform>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Padding="5" Background="Transparent">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="tileBackground" Grid.RowSpan="2" Background="{TemplateBinding Background}" Opacity="0.9" />
<Image Source="{Binding Image}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="50" />
<ContentPresenter TextElement.Foreground="White" Grid.Row="1" HorizontalAlignment="Center" Margin="3,10" />
</Grid>
</Border>
</ControlTemplate>
</Button.Template>
<Button.Resources>
<ElasticEase x:Key="easeOutBounce" EasingMode="EaseOut" Springiness="6" Oscillations="4" />
</Button.Resources>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard Duration="00:00:00.05" AutoReverse="True">
<DoubleAnimation To="0.1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
<DoubleAnimation To="0.1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
ViewModel, TileModel, TileType, ActionCommand:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<TileModel> _tiles;
public ObservableCollection<TileModel> Tiles { get { return _tiles; } set { _tiles = value; OnPropertyChanged("Tiles"); } }
public ViewModel()
{
Tiles= new ObservableCollection<TileModel>()
{
new TileModel() { Text = "Facebook", Image = Properties.Resources.Facebook.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Skype", Image = Properties.Resources.Skype.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Ask.com", Image = Properties.Resources.AskCom.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Amazon", Image = Properties.Resources.Amazon.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Evernote", Image = Properties.Resources.Evernote.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Twitter", Image = Properties.Resources.Twitter.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Internet Explorer", Image = Properties.Resources.InterneExplorer.ToBitmapImage(), TileType = TileType.Browser },
new TileModel() { Text = "Android", Image = Properties.Resources.Android.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Winamp", Image = Properties.Resources.Winamp.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "YouTube", Image = Properties.Resources.YouTube.ToBitmapImage(), TileType = TileType.Website },
};
}
}
public class TileModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _text;
public string Text { get { return _text; } set { _text = value; OnPropertyChanged("Text"); } }
private BitmapSource _image;
public BitmapSource Image { get { return _image; } set { _image = value; OnPropertyChanged("Image"); } }
private TileType _tileType;
public TileType TileType { get { return _tileType; } set { _tileType = value; OnPropertyChanged("TileType"); } }
public ICommand ClickCommand { get; private set; }
public TileModel()
{
ClickCommand = new ActionCommand(Click);
}
private void Click()
{
// execute appropriate action
}
}
public enum TileType
{
Browser,
Website,
Application
}
public class ActionCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action _action;
public ActionCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter)
{
if (_action != null)
_action();
}
}
}
ItemsControlDragDropBehavior:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace WpfApplication1.Behavior
{
public class ItemsControlDragDropBehavior : Behavior<ItemsControl>
{
private bool _isMouseDown;
private bool _isDragging;
private Point _dragStartPosition;
private UIElement _dragItem;
private UIElement _dragContainer;
private IDataObject _dataObject;
private int _currentDropIndex;
private Point _lastCheckPoint;
protected override void OnAttached()
{
this.AssociatedObject.AllowDrop = true;
this.AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
this.AssociatedObject.PreviewMouseMove += AssociatedObject_PreviewMouseMove;
this.AssociatedObject.PreviewDragOver += AssociatedObject_PreviewDragOver;
this.AssociatedObject.PreviewDrop += AssociatedObject_PreviewDrop;
this.AssociatedObject.PreviewMouseLeftButtonUp += AssociatedObject_PreviewMouseLeftButtonUp;
base.OnAttached();
}
void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl itemsControl = (ItemsControl)sender;
Point p = e.GetPosition(itemsControl);
object data = itemsControl.GetDataObjectFromPoint(p);
_dataObject = data != null ? new DataObject(data.GetType(), data) : null;
_dragContainer = itemsControl.GetItemContainerFromPoint(p);
if (_dragContainer != null)
_dragItem = GetItemFromContainer(_dragContainer);
if (data != null)
{
_isMouseDown = true;
_dragStartPosition = p;
}
}
void AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
ItemsControl itemsControl = (ItemsControl)sender;
Point currentPosition = e.GetPosition(itemsControl);
if ((_isDragging == false) && (Math.Abs(currentPosition.X - _dragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(currentPosition.Y - _dragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance))
{
DragStarted(e.GetPosition(itemsControl));
}
e.Handled = true;
}
}
void AssociatedObject_PreviewDragOver(object sender, DragEventArgs e)
{
UpdateDropIndex(e.GetPosition(this.AssociatedObject));
}
void AssociatedObject_PreviewDrop(object sender, DragEventArgs e)
{
UpdateDropIndex(e.GetPosition(this.AssociatedObject));
}
void AssociatedObject_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_isMouseDown = false;
}
private void DragStarted(Point p)
{
if (!_isDragging)
{
_isDragging = true;
if (_dragContainer != null)
_dragContainer.Opacity = 0.3;
_currentDropIndex = FindDropIndex(p);
DragDropEffects e = DragDrop.DoDragDrop(this.AssociatedObject, _dataObject, DragDropEffects.Copy | DragDropEffects.Move);
ResetState();
}
}
private void ResetState()
{
if (_dragContainer != null)
_dragContainer.Opacity = 1.0;
_isMouseDown = false;
_isDragging = false;
_dataObject = null;
_dragItem = null;
_dragContainer = null;
_currentDropIndex = -1;
}
private void UpdateDropIndex(Point p)
{
if ((_lastCheckPoint - p).Length > SystemParameters.MinimumHorizontalDragDistance) // prevent too frequent call
{
int dropIndex = FindDropIndex(p);
if (dropIndex != _currentDropIndex && dropIndex > -1)
{
this.AssociatedObject.RemoveItem(_dataObject);
this.AssociatedObject.AddItem(_dataObject, dropIndex);
_currentDropIndex = dropIndex;
}
_lastCheckPoint = p;
}
}
private int FindDropIndex(Point p)
{
ItemsControl itemsControl = this.AssociatedObject;
UIElement dropTargetContainer = null;
dropTargetContainer = itemsControl.GetItemContainerFromPoint(p);
int index = -1;
if (dropTargetContainer != null)
{
index = itemsControl.ItemContainerGenerator.IndexFromContainer(dropTargetContainer);
if (!IsPointInTopHalf(p))
index = index++; // in second half of item, add after
}
else if (IsPointAfterAllItems(itemsControl, p))
{
// still within itemscontrol, but after all items
index = itemsControl.Items.Count - 1;
}
return index;
}
public bool IsPointInTopHalf(Point p)
{
ItemsControl itemsControl = this.AssociatedObject;
bool isInTopHalf = false;
UIElement selectedItemContainer = itemsControl.GetItemContainerFromPoint(p);
Point relativePosition = Mouse.GetPosition(selectedItemContainer);
if (IsItemControlOrientationHorizontal())
isInTopHalf = relativePosition.X < ((FrameworkElement)selectedItemContainer).ActualWidth / 2;
else
isInTopHalf = relativePosition.Y < ((FrameworkElement)selectedItemContainer).ActualHeight / 2;
return isInTopHalf;
}
private bool IsItemControlOrientationHorizontal()
{
bool isHorizontal = false;
Panel panel = GetItemsPanel();
if (panel is WrapPanel)
isHorizontal = ((WrapPanel)panel).Orientation == Orientation.Horizontal;
else if (panel is StackPanel)
isHorizontal = ((StackPanel)panel).Orientation == Orientation.Horizontal;
return isHorizontal;
}
private UIElement GetItemFromContainer(UIElement container)
{
UIElement item = null;
if (container != null)
item = VisualTreeHelper.GetChild(container, 0) as UIElement;
return item;
}
private Panel GetItemsPanel()
{
ItemsPresenter itemsPresenter = GetVisualChild<ItemsPresenter>(this.AssociatedObject);
Panel itemsPanel = VisualTreeHelper.GetChild(itemsPresenter, 0) as Panel;
return itemsPanel;
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
/// still needs some work
private static bool IsPointAfterAllItems(ItemsControl itemsControl, Point point)
{
bool isAfter = false;
UIElement target = itemsControl.GetLastItemContainer();
Point targetPos = target.TransformToAncestor(itemsControl).Transform(new Point(0, 0));
Point relativeToTarget = new Point(point.X - targetPos.X, point.Y - targetPos.Y);
if (relativeToTarget.X >= 0 && relativeToTarget.Y >= 0)
{
var bounds = VisualTreeHelper.GetDescendantBounds(target);
isAfter = !bounds.Contains(relativeToTarget);
}
return isAfter;
}
}
public static class ItemsControlExtensions
{
public static object GetDataObjectFromPoint(this ItemsControl itemsControl, Point p)
{
UIElement element = itemsControl.InputHitTest(p) as UIElement;
while (element != null)
{
if (element == itemsControl)
return null;
object data = itemsControl.ItemContainerGenerator.ItemFromContainer(element);
if (data != DependencyProperty.UnsetValue)
return data;
else
element = VisualTreeHelper.GetParent(element) as UIElement;
}
return null;
}
public static UIElement GetItemContainerFromPoint(this ItemsControl itemsControl, Point p)
{
UIElement element = itemsControl.InputHitTest(p) as UIElement;
while (element != null)
{
object data = itemsControl.ItemContainerGenerator.ItemFromContainer(element);
if (data != DependencyProperty.UnsetValue)
return element;
else
element = VisualTreeHelper.GetParent(element) as UIElement;
}
return element;
}
public static UIElement GetLastItemContainer(this ItemsControl itemsControl)
{
UIElement container = null;
if (itemsControl.HasItems)
container = itemsControl.GetItemContainerAtIndex(itemsControl.Items.Count - 1);
return container;
}
public static UIElement GetItemContainerAtIndex(this ItemsControl itemsControl, int index)
{
UIElement container = null;
if (itemsControl != null && itemsControl.Items.Count > index && itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
container = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as UIElement;
else
container = itemsControl;
return container;
}
public static void AddItem(this ItemsControl itemsControl, IDataObject item, int insertIndex)
{
if (itemsControl.ItemsSource != null)
{
foreach (string format in item.GetFormats())
{
object data = item.GetData(format);
IList iList = itemsControl.ItemsSource as IList;
if (iList != null)
iList.Insert(insertIndex, data);
else
{
Type type = itemsControl.ItemsSource.GetType();
Type genericList = type.GetInterface("IList`1");
if (genericList != null)
type.GetMethod("Insert").Invoke(itemsControl.ItemsSource, new object[] { insertIndex, data });
}
}
}
else
itemsControl.Items.Insert(insertIndex, item);
}
public static void RemoveItem(this ItemsControl itemsControl, IDataObject itemToRemove)
{
if (itemToRemove != null)
{
foreach (string format in itemToRemove.GetFormats())
{
object data = itemToRemove.GetData(format);
int index = itemsControl.Items.IndexOf(data);
if (index > -1)
itemsControl.RemoveItemAt(index);
}
}
}
public static void RemoveItemAt(this ItemsControl itemsControl, int removeIndex)
{
if (removeIndex != -1 && removeIndex < itemsControl.Items.Count)
{
if (itemsControl.ItemsSource != null)
{
IList iList = itemsControl.ItemsSource as IList;
if (iList != null)
{
iList.RemoveAt(removeIndex);
}
else
{
Type type = itemsControl.ItemsSource.GetType();
Type genericList = type.GetInterface("IList`1");
if (genericList != null)
type.GetMethod("RemoveAt").Invoke(itemsControl.ItemsSource, new object[] { removeIndex });
}
}
else
itemsControl.Items.RemoveAt(removeIndex);
}
}
}
}
TileTypeToColorConverter:
public class TileTypeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush();
TileType type = (TileType)value;
switch (type)
{
case TileType.Browser: brush.Color = Colors.Maroon; break;
case TileType.Application: brush.Color = Colors.DodgerBlue; break;
case TileType.Website: brush.Color = Colors.DarkGoldenrod; break;
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can simply use Item Control,
i). In Item Panel just give the number of rows and columns you want
ii). If button you want here are to be generated dynamically just assign the list of Buttons to it.
<ItemsControl x:Name="lstButtons"
Grid.Row="0"
Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"
Rows="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Click="CLICK_EVENT_HERE"
Style="Use metro Button Style here" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Related
I am learning WPF and MVVM, and have written a simple Type-Select List box in WPF as a learning practice program.
Though it works,I have three questions which are as under:-
1)How to set the Text property of TxtMail via DataBinding?Currently it messes up with other logic if i set Text property in XAML via DataBinding.I do not want to set the text property of Txtmail directly from Code-Behind,while setting the same via DataBinding in XAML messes up things owing to my limited understanding of the subject.
2)The ItemSource of ListBox named AllMatching is being set from Code-Behind since it is changing programatically with text search patterns.How can i set it from XAML?
3)Is there a way i can remove the logic of GUI Control Events and include the same in XAML?
The entire code is as under:-
ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public List<DM_Data> DynamicData;
public Visibility p_ListVisibility = Visibility.Collapsed;
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public Visibility ListVisibility
{
get { return p_ListVisibility; }
set
{
p_ListVisibility = p_ListVisibility != value ? value : p_ListVisibility;
RaisePropertyChangedEvent("ListVisibility");
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
DynamicData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = DynamicData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
DataModel:
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public Visibility p_ListVisibility = Visibility.Visible;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public Visibility ListVisibility
{
get { return p_ListVisibility; }
set { p_ListVisibility = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
}
MainWindow.Xaml
<Window x:Class="TypeSelect.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:TypeSelect"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="25" GotFocus="TxtMail_GotFocus" LostFocus="TxtMail_LostFocus" KeyUp="TxtMail_KeyUp" MouseUp="TxtMail_MouseUp" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" MinHeight="10" MaxHeight="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" Visibility="{Binding ListVisibility,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" KeyUp="AllMatching_KeyUp" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" MouseUp="AllMatching_MouseUp" GotFocus="AllMatching_GotFocus" MouseDoubleClick="AllMatching_MouseDoubleClick" ForceCursor="True">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontFamily" Value="Arial Bold" />
<Setter Property="Background" Value="LightGray" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="18" />
</Trigger>
</Style.Triggers>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="LightGray"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightGray"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White" />
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
MainWindow.Xaml.cs
namespace TypeSelect
{
public partial class MainWindow : Window
{
VM_Data ViewModel;
DM_Data Existing;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.DynamicData;
ViewModel.CurrentRec = Existing= ViewModel.AllData[new Random().Next(0, ViewModel.AllData.Count - 1)];
TxtMail.Text = (ViewModel.CurrentRec != null) ? ViewModel.CurrentRec.Name : "";
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
}
private void TxtMail_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Enter))
{
ViewModel.CurrentRec = (DM_Data)AllMatching.SelectedItem;
((TextBox)sender).Text = (ViewModel.CurrentRec != null) ? ViewModel.CurrentRec.Name : "";
//ViewModel.CurrentRec.SearchText = ViewModel.CurrentRec.Name;
ViewModel.ListVisibility = ViewModel.ListVisibility.Equals(Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
if (ViewModel.ListVisibility.Equals(Visibility.Visible))
{
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
else
{
Existing = ViewModel.CurrentRec;
}
}
else if (e.Key.Equals(Key.Escape))
{
ViewModel.CurrentRec = Existing;
ViewModel.ListVisibility = Visibility.Collapsed;
((TextBox)sender).Text = (ViewModel.CurrentRec != null) ? ViewModel.CurrentRec.Name : "";
}
else if (e.Key.Equals(Key.Up))
{
AllMatching.SelectedIndex = AllMatching.SelectedIndex > 0 ? AllMatching.SelectedIndex - 1 : AllMatching.SelectedIndex;
ViewModel.CurrentRec = (DM_Data)AllMatching.SelectedItem;
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
else if (e.Key.Equals(Key.Down))
{
AllMatching.SelectedIndex = AllMatching.SelectedIndex < AllMatching.Items.Count ? AllMatching.SelectedIndex + 1 : AllMatching.SelectedIndex;
ViewModel.CurrentRec = (DM_Data)AllMatching.SelectedItem;
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
else
{
ViewModel.DynamicData = ViewModel.AllData.Where(item => item.Name.StartsWith(TxtMail.Text, StringComparison.OrdinalIgnoreCase)).ToList<DM_Data>();
AllMatching.ItemsSource = ViewModel.DynamicData;
ViewModel.CurrentRec = (AllMatching.Items.Count > 0) ? ViewModel.DynamicData.Where(item => item.Name.StartsWith(TxtMail.Text, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() : null;
if (AllMatching.Visibility.Equals(Visibility.Collapsed))
{
AllMatching.Visibility = Visibility.Visible;
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
}
}
private void TxtMail_GotFocus(object sender, RoutedEventArgs e)
{
DM_Data Existing = ViewModel.CurrentRec;
ViewModel.ListVisibility = Visibility.Visible;
}
private void TxtMail_LostFocus(object sender, RoutedEventArgs e)
{
ViewModel.ListVisibility = Visibility.Collapsed;
}
private void TxtMail_MouseUp(object sender, MouseButtonEventArgs e)
{
ViewModel.ListVisibility = Visibility.Visible;
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
private void AllMatching_MouseUp(object sender, MouseButtonEventArgs e)
{
ViewModel.CurrentRec = (DM_Data)AllMatching.SelectedItem;
ViewModel.ListVisibility = Visibility.Visible;
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
private void AllMatching_GotFocus(object sender, RoutedEventArgs e)
{
ViewModel.ListVisibility = Visibility.Visible;
}
private void AllMatching_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Enter))
{
TxtMail.Text = ViewModel.CurrentRec.Name;
ViewModel.ListVisibility = Visibility.Collapsed;
}
}
private void AllMatching_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ViewModel.CurrentRec = (DM_Data)AllMatching.SelectedItem;
TxtMail.Text = (ViewModel.CurrentRec != null) ? ViewModel.CurrentRec.Name : "";
//ViewModel.CurrentRec.SearchText = ViewModel.CurrentRec.Name;
ViewModel.ListVisibility = ViewModel.ListVisibility.Equals(Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
if (ViewModel.ListVisibility.Equals(Visibility.Visible))
{
AllMatching.ScrollIntoView(ViewModel.CurrentRec);
}
else
{
Existing = ViewModel.CurrentRec;
}
}
}
}
How to set the Text property of TxtMail via DataBinding?
Bind it to a source property of the view model:
<TextBox x:Name="TxtMail" Binding="{Binding Mail}" />
Mail is a property of the VM_Data class, just like Name.
The ItemSource of ListBox named AllMatching is being set from Code-Behind since it is changing programatically with text search patterns. How can i set it from XAML?
You should bind this one as well:
<ListBox x:Name="AllMatching" ItemsSource="{Binding DynamicData}" ...>
For this to work, DynamicData must be a property:
public List<DM_Data> DynamicData { get; set; }
If you are adding items dynamically to it, you should make it an ObservableCollection:
public ObservableCollection<DM_Data> DynamicData { get; set; }
Is there a way i can remove the logic of GUI Control Events and include the same in XAML?
XAML is a markup language. It is not a programming language. But you should look into commands: https://blog.magnusmontin.net/2013/06/30/handling-events-in-an-mvvm-wpf-application/.
I have a datagrid. I want to add Tab when row double click event is rised. When AddTab method is called Tab is added to ObservableCollection, but it doesn't show up on TabControl. Why it doesn't show up? Because there is a wrong DataContext when method called?
<DataGrid helpers:RowDoubleClickHandler.MethodName="AddTab" AutoGenerateColumns="False"
IsSynchronizedWithCurrentItem="True" CanUserResizeRows="True" x:Name="dataGrid1"
ItemsSource="{Binding DataGridEntries3}" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" IsReadOnly="True">
AddTab method in ViewModel
public void AddTab()
{
Tabs.Add(new TabEntry
{
Description = "Tab3",
DataGridEntries = new ObservableCollection<DataGridEntry>()
{
new DataGridEntry()
{
}
}
});
XAML with TabControl
<Window x:Class="ProjectZero.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ProjectZero"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<vm:MainViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<ToolBar Height="40" VerticalAlignment="Top">
<Menu>
<MenuItem Header="Menu" Margin="6" Foreground="White" FontSize="14" FontFamily="Times New Roman">
<MenuItem Header="Add Invoice" Command="{Binding AddInvoice}"/>
<MenuItem Header="Invoices List" Command="{Binding AddInvoiceList}" FontFamily="Tahoma" />
</MenuItem>
<Menu.Background>
<SolidColorBrush />
</Menu.Background>
</Menu>
<ToolBar.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="#FF173ADE" Offset="0.431" />
<GradientStop Color="#FF0B1D6F" Offset="0.646" />
</LinearGradientBrush>
</ToolBar.Background>
</ToolBar>
<TabControl x:Name="tabControl1" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Tabs}" ItemTemplate="{DynamicResource DataTemplateType1}" TabStripPlacement="Top" HorizontalAlignment="Stretch" Margin="10,46,0,0" VerticalAlignment="Stretch" Width="Auto">
</TabControl>
ViewModel for Tabs
public class MainViewModel : BaseViewModel
{
public RelayCommand RelayCommand { get; set; }
public MainViewModel()
{
this.RelayCommand = new RelayCommand(this);
Tabs.CollectionChanged += (o, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (BaseViewModel item in e.NewItems)
if (item.MyType == "TabEntry")
DataGridEntries3.Add(item as TabEntry);
}
};
var t = (from i in Tabs where i.MyType == "TabEntry" select (TabEntry)i);
DataGridEntries3 = new ObservableCollection<TabEntry>(t);
}
private ObservableCollection<BaseViewModel> _tabs;
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs != null ? _tabs : _tabs = new ObservableCollection<BaseViewModel>(); }
set { _tabs = value; OnPropertyChanged("Tabs"); }
}
BaseViewModel _SelectedItem;
public BaseViewModel SelectedItem
{
get { return _SelectedItem; }
set { _SelectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private ObservableCollection<TabEntry> _DataGridEntries3;
public ObservableCollection<TabEntry> DataGridEntries3
{
get { return _DataGridEntries3 != null ? _DataGridEntries3 : _DataGridEntries3 = new ObservableCollection<TabEntry>(); }
set { _DataGridEntries3 = value; OnPropertyChanged("DataGridEntries3"); }
}
ICommand _AddInvoice = null;
ICommand _AddInvoiceList = null;
public ICommand AddInvoice
{
get
{
return _AddInvoice != null ? _AddInvoice : _AddInvoice.SetCommand(param =>
{
Tabs.Add(new TabEntry
{
Description = "Tab3",
DataGridEntries = new ObservableCollection<DataGridEntry>()
{
new DataGridEntry()
{
}
}
});
});
}
}
public ICommand AddInvoiceList
{
get
{
return _AddInvoiceList != null ? _AddInvoiceList : _AddInvoiceList.SetCommand(param =>
{
var tab_dc = Tabs.FirstOrDefault(it => it.GetType() == typeof(MainViewModel));
if (tab_dc != null)
{
Tabs.Add(tab_dc);
}
else
{
var new_tab = new MainViewModel();
Tabs.Add(new_tab);
}
});
}
}
}
Also here is RowDoubleClick Event Handler
public sealed class RowDoubleClickHandler : FrameworkElement
{
public RowDoubleClickHandler(DataGrid dataGrid)
{
MouseButtonEventHandler handler = (sender, args) =>
{
var row = sender as DataGridRow;
if (row != null && row.IsSelected)
{
var methodName = GetMethodName(dataGrid);
var dataContextType = dataGrid.DataContext.GetType();
var method = dataContextType.GetMethod(methodName);
if (method == null)
{
throw new MissingMethodException(methodName);
}
method.Invoke(dataGrid.DataContext, null);
}
};
dataGrid.LoadingRow += (s, e) =>
{
e.Row.MouseDoubleClick += handler;
};
dataGrid.UnloadingRow += (s, e) =>
{
e.Row.MouseDoubleClick -= handler;
};
}
public static string GetMethodName(DataGrid dataGrid)
{
return (string)dataGrid.GetValue(MethodNameProperty);
}
public static void SetMethodName(DataGrid dataGrid, string value)
{
dataGrid.SetValue(MethodNameProperty, value);
}
public static readonly DependencyProperty MethodNameProperty = DependencyProperty.RegisterAttached(
"MethodName",
typeof(string),
typeof(RowDoubleClickHandler),
new PropertyMetadata((o, e) =>
{
var dataGrid = o as DataGrid;
if (dataGrid != null)
{
new RowDoubleClickHandler(dataGrid);
}
}));
}
A few tips, since you asked in your comment:
This:
private ObservableCollection<BaseViewModel> _tabs;
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs != null ? _tabs : _tabs = new ObservableCollection<BaseViewModel>(); }
set { _tabs = value; OnPropertyChanged("Tabs"); }
}
Could be rewritten a lot cleaner as:
public ObservableCollection<BaseViewModel> Tabs { get; private set; }
With this added to your constructor:
Tabs = new ObservableCollection<BaseViewModel>();
This is a consistent theme in your code. Basically, don't make modifications in your property getters/setters unless you really really need to. And in your case, all it is doing is complicating things.
It is especially confusing when you have a getter that then will add a tab to your collection of tabs when first invoked. That is very difficult to follow programmatically. Just do your initialization in your constructor like everyone else does :)
I'm using a ListBox control in WPF to show something, and each items in this ListBox has a same height, how can I know which item is on the top of the current ListBox view when I drag the scroll-bar? Thank you.
<Grid DataContext="{Binding ElementName=This}">
<StackPanel>
<ListBox Height="100" ScrollViewer.ScrollChanged="ListBox_ScrollChanged">
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
<ListBoxItem>3</ListBoxItem>
<ListBoxItem>4</ListBoxItem>
<ListBoxItem>5</ListBoxItem>
<ListBoxItem>6</ListBoxItem>
<ListBoxItem>7</ListBoxItem>
<ListBoxItem>8</ListBoxItem>
<ListBoxItem>9</ListBoxItem>
<ListBoxItem>10</ListBoxItem>
<ListBoxItem>11</ListBoxItem>
<ListBoxItem>12</ListBoxItem>
<ListBoxItem>13</ListBoxItem>
<ListBoxItem>14</ListBoxItem>
<ListBoxItem>15</ListBoxItem>
<ListBoxItem>16</ListBoxItem>
<ListBoxItem>17</ListBoxItem>
<ListBoxItem>18</ListBoxItem>
</ListBox>
<TextBlock Text="{Binding TopMostItem}"/>
</StackPanel>
</Grid>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private String _topMost;
public String TopMostItem
{
get { return _topMost; }
set { _topMost = value; RaisePropertyChanged("TopMostItem"); }
}
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var lb = sender as ListBox;
foreach (var lbi in lb.Items)
{
var container = lb.ItemContainerGenerator.ContainerFromItem(lbi) as ListBoxItem;
if (container != null && IsUserVisible(container, lb))
{
TopMostItem = container.Content as String;
return;
}
}
}
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
Rect fudgybounds = new Rect(new Point(bounds.TopLeft.X, bounds.TopLeft.Y), new Point(bounds.BottomRight.X, bounds.BottomRight.Y - 5));
return rect.Contains(fudgybounds.TopLeft) || rect.Contains(fudgybounds.BottomRight);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(name));
}
#endregion
IsUserVisible method courtesy of:
Find WPF Controls in Viewport
You might use this code:
public static FrameworkElement GetFirstItem(ListBox listBox)
{
if (listBox.Items.Count == 0)
return null;
VirtualizingStackPanel vsp = VisualTreeHelper.GetParent(listBox.Items[0] as FrameworkElement) as VirtualizingStackPanel;
if (vsp != null)
{
int fvi = (int)vsp.VerticalOffset;
int vic = (int)vsp.ViewportHeight;
int index=-1;
foreach (FrameworkElement item in listBox.Items)
{
index++;
if (index >= fvi && index <= fvi + vic)
{
return item;
}
}
}
return null;
}
I know you can achieve this in Silverlight 4 by playing with the ListBoxItem style's LayoutStates, i.e. BeforeUnloaded, BeforeLoaded and AfterLoaded.
It doesn't seem to be working at all in WP7 although these states exist in the default style.
I am currently using version 7.1.
Is there any way I can get this working?
Thanks,
Xin
for this I used Artefact Animator, it's for Silverlight but works perfectly for WP7 also. The code shows only the addition. Whole code from the project's sample page.
MainPage.xaml
<UserControl.Resources>
<!-- ADDS SMOOTH SCROLL -->
<ItemsPanelTemplate x:Key="ItemsPanelTemplate">
<StackPanel/>
</ItemsPanelTemplate>
</UserControl.Resources>
<Grid>
<ListBox x:Name="lb" Height="247" Width="100" ItemsPanel="{StaticResource ItemsPanelTemplate}" />
<Button x:Name="addBtn" Content="Add" Height="72" HorizontalAlignment="Left" Margin="159,145,0,0" VerticalAlignment="Top" Width="160" />
</Grid>
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
private static ScrollViewer _scrollViewer;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// INIT
lb.Items.Clear();
lb.UpdateLayout();
// SCROLL INTERACTION
_scrollViewer = FindVisualChild<ScrollViewer>(lb);
var bar = FindVisualChild<ScrollBar>(_scrollViewer);
if (bar != null)
bar.ValueChanged += (s, args) => SetValue(ListBoxScrollOffsetProperty, args.NewValue);
// INPUT
addBtn.Click += (s, args) => AddItem();
}
private void AddItem()
{
// Create New ListBoxItem
var lbi = new ListBoxItem
{
Content = "Item " + lb.Items.Count,
RenderTransform = new CompositeTransform
{
TranslateX = -lb.Width
},
};
// Add ListBoxItem
lb.Items.Add(lbi);
lb.UpdateLayout();
// Animate In Item
ArtefactAnimator.AddEase(lbi.RenderTransform, CompositeTransform.TranslateXProperty, 0, 1, AnimationTransitions.CubicEaseOut, 0);
ArtefactAnimator.AddEase(this, ListBoxScrollOffsetProperty, _scrollViewer.ScrollableHeight, .8, AnimationTransitions.CubicEaseOut, 0);
}
// LISTBOX SCROLL OFFSET
public static readonly DependencyProperty ListBoxScrollOffsetProperty =
DependencyProperty.Register("ListBoxScrollOffset", typeof(double), typeof(MainPage), new PropertyMetadata(0.0, OnListBoxScrollOffsetChanged));
private static void OnListBoxScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
public double ListBoxScrollOffset
{
get
{
return (double)GetValue(ListBoxScrollOffsetProperty);
}
set
{
SetValue(ListBoxScrollOffsetProperty, value);
}
}
// VISUAL HELPER
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
var childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
}
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;
}
}
}