I have a WPF UI Bound to a collection of AwesomeClass
Now, AwesomeClass has a collection of AwesomeParts objects.
How can I make my UI In such a way that (as an example)
for each AwesomeClass instance, there is a Tab on a tab panel
and then for each awesome part in that, there is an object on a listbox, on that tab.
Basically: Awesomes->Tabs
And Then : Awesome.Awesomeparts->Tabs.Listbox
Following is the code to do what you are looking for :
public partial class TabWindow : Window
{
public TabWindow()
{
InitializeComponent();
List<AwesomeClass> items = new List<AwesomeClass>();
for (int i = 0; i < 10; i++)
{
items.Add(new AwesomeClass());
}
AwesomeTabs.ItemsSource = items;
Loaded += new RoutedEventHandler(TabWindow_Loaded);
}
// Method 1
void TabWindow_Loaded(object sender, RoutedEventArgs e)
{
FindListBox(AwesomeTabs);
}
private void FindListBox(DependencyObject obj)
{
Int32 count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is ListBox)
{
(child as ListBox).SelectionChanged += new SelectionChangedEventHandler(ListBox_SelectionChanged);
}
else
{
FindListBox(child);
}
}
}
// Method 2
private void ListBox_Loaded(object sender, RoutedEventArgs e)
{
(sender as ListBox).SelectionChanged += new SelectionChangedEventHandler(ListBox_SelectionChanged);
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
MessageBox.Show(e.AddedItems[0].ToString());
}
catch (Exception)
{ }
}
}
class AwesomeClass
{
static Int32 count = 0;
public Int32 Index { get; set; }
public List<AwesomePart> Parts { get; protected set; }
// Method 3 : Preferred
private AwesomePart _selectedPart;
public AwesomePart SelectedPart
{
get { return _selectedPart; }
set
{
OnSelectionChanged(_selectedPart, value);
_selectedPart = value;
}
}
private void OnSelectionChanged(AwesomePart oldValue, AwesomePart newValue)
{
if (newValue != null) MessageBox.Show(newValue.ToString());
}
public AwesomeClass()
{
Index = ++count;
Parts = new List<AwesomePart>();
for (int i = 0; i < 10; i++)
{
Parts.Add(new AwesomePart());
}
}
public override string ToString()
{
return "Tab #" + Index.ToString();
}
}
class AwesomePart
{
static Int32 count = 0;
public Int32 Index { get; set; }
public AwesomePart()
{
Index = ++count;
}
public override string ToString()
{
return "Part #" + Index.ToString();
}
}
XAML:
<Grid>
<TabControl Name="AwesomeTabs">
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Parts}" SelectedItem="{Binding SelectedPart}" Loaded="ListBox_Loaded"></ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Bind a List<AwesomeClass> to a headered content control. Each "AwesomeClass" object will be set as the datacontext for each "tab" in the headered content control.
Within the content control that is on each "tab", bind the DataContext (AwesomeClass) property that accesses the List<AwesomePart> to your Listbox control.
Make sense?
Cheers.
Related
I have a PropertyGrid control, which has its properties defined in a class, like this:
[DisplayName("Display Company Logo")]
[PropertyOrder(5)]
public bool HasLogo { get; set; }
[DisplayName("Logo File Path")]
[PropertyOrder(6)]
[Browsable(true)]
[Editor(typeof(FilePickerEditor), typeof(FilePickerEditor))]
public string LogoFilePath { get; set; }
Is it possible to hide LogoFilePath property, depending on whether HasLogo is checked or not? Or, at least, make custom FilePickerEditor read-only.
I was able to do solve that using a behavior. PropertyGrid defined like this:
<toolkitExt:PropertyGrid Tag="DependentVisibility,HasLogo,LogoFilePath"
SelectedObject="{Binding PropertyGridSourceObjectUserPreferences, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Behaviors>
<b:PropertyGridBehavior/>
</i:Interaction.Behaviors>
</toolkitExt:PropertyGrid>
Behavior:
public class PropertyGridBehavior : Behavior<PropertyGrid>
{
string _tag;
List<Tuple<string, string, string>> _dependentVisibilityList;
PropertyGrid _propertyGrid;
protected override void OnAttached()
{
_propertyGrid = AssociatedObject as PropertyGrid;
_dependentVisibilityList = new List<Tuple<string, string, string>>();
if(_propertyGrid.Tag !=null)
{
_tag = _propertyGrid.Tag.ToString();
foreach(var v in _tag.Split(';'))
{
if (v.Split(',').Count() != 3) return;
_dependentVisibilityList.Add(new Tuple<string, string, string>(v.Split(',')[0], v.Split(',')[1], v.Split(',')[2]));
}
}
_propertyGrid.Loaded += _propertyGrid_Loaded;
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
}
private void _propertyGrid_Loaded(object sender, RoutedEventArgs e)
{
foreach(var v in _propertyGrid.Properties as PropertyItemCollection)
{
PropertyItemDependencyVisibilitySet(v.PropertyName, v.Value);
}
}
private void PropertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
if (e.NewValue.GetType().Equals(typeof(bool)))
{
PropertyItem originalSource = (PropertyItem)e.OriginalSource;
PropertyItemDependencyVisibilitySet(originalSource.PropertyName, (bool)e.NewValue);
}
}
private void PropertyItemDependencyVisibilitySet(string propertyName, object propertyValue)
{
try
{
Tuple<string, string, string> dependentVisibilityItem = _dependentVisibilityList.Where(x => x.Item1 == "DependentVisibility" && x.Item2 == propertyName).FirstOrDefault();
if (dependentVisibilityItem != null)
{
PropertyItemCollection propertyCollection = _propertyGrid.Properties as PropertyItemCollection;
PropertyItem propertyItemDestination = propertyCollection.Where(x => x.PropertyName == dependentVisibilityItem.Item3).FirstOrDefault();
if (propertyItemDestination != null) propertyItemDestination.Visibility = (bool) propertyValue ? Visibility.Visible : Visibility.Collapsed;
}
}
catch
{
}
}
}
I have color picker. If I selected different colors means it is fire color changed event. But I select color is already selected color, the Color changed event is not fired. So how can I achieve this requirement or how can hook event for when select color is already selected.
Xaml:
<system:SplitButton x:Name="Font_FontColor" Height="24" DataContext="{Binding ElementName=Font_FontColorPicker}">
<system:ColorPickerPalette x:Name="Font_FontColorPicker" system:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both"
IsExpanded="True"
MoreColorOptionVisibility="Collapsed"/>
C#
it is a control behavior. so can make the code for achieve this by below codes
XAML:
<syncfusion:SplitButton x:Name="Font_FontColor" Height="24"
DataContext="{Binding ElementName=Font_FontColorPicker}">
<syncfusion:ColorPickerPalette x:Name="Font_FontColorPicker"
syncfusion:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both" IsExpanded="True" MoreColorOptionVisibility="Collapsed"
Color="Red" />
C#:
public partial class MainWindow : Window
{
bool CanHookEvents = true;
public MainWindow()
{
InitializeComponent();
Font_FontColorPicker.ColorChanged += Font_FontColorPicker_ColorChanged;
//Font_FontColor.IsDropDownOpenChanged += Font_FontColor_IsDropDownOpenChanged;
Font_FontColorPicker.Loaded += Font_FontColorPicker_Loaded;
}
private void Font_FontColorPicker_Loaded(object sender, RoutedEventArgs e)
{
if (CanHookEvents)
{
foreach (ColorGroupItem item in FindVisualChildrenOfType<ColorGroupItem>(Font_FontColorPicker))
{
if (item != null)
{
item.PreviewMouseLeftButtonDown += item_PreviewMouseLeftButtonDown;
}
}
}
}
void item_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if(Font_FontColorPicker.Color.Equals((((sender as ColorGroupItem).Color) as SolidColorBrush).Color))
{
// I have closed dropdown. Do your stuff here
Font_FontColor.IsDropDownOpen = false;
}
}
public static IEnumerable<T> FindVisualChildrenOfType<T>(DependencyObject parent)
where T : DependencyObject
{
List<T> foundChildren = new List<T>();
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foreach (var other in FindVisualChildrenOfType<T>(child))
yield return other;
}
else
{
yield return (T)child;
}
}
}
void Font_FontColorPicker_ColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Font_FontColor.IsDropDownOpen = false;
}
}
Here are the requirements.
In my ItemsControl (you can use a ListView if it helps you to consider the scenario). I want to inject, not a record, but a unique DataTemplate into an arbitrary indexed location in my list.
For example, I might want to insert it into the first position, index 0, or the third position, index 2, or perhaps even have the logic to insert it into the last position, index count-1.
I will need to sub-class ListView to accomplish this, I realize. That being said, I could easily create the SpecialItemTemplate, SpecialItemIndex DP properties to have the values.
Added requirements:
Don't require a special type of collection
Don't require manipulating the existing data
Allow the IsHitTestVisible to be variable, too
Any ideas how to accomplish this feat (in WinRT)?
Here is a solution that is basically a Behavior with a Template property which can be attached to any ItemsControl. I tested it with virtualizing and non-virtualizing panels, works for both cases. If you think the code is convoluted... well I can't disagree, wrote it a while back and I can't remember what reasons I had to write it the way it ended up.
Usage:
<ListBox ItemsSource="{Binding Persons}">
<Interactivity:Interaction.Behaviors>
<AlternativeItemTemplate Index="42">
<DataTemplate>
...foo...
</DataTemplate>
</AlternativeItemTemplate>
</Interactivity:Interaction.Behaviors>
<ListBox.ItemTemplate>
<DataTemplate>
...bar...
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and the classes:
[ContentProperty( "ItemTemplate" )]
public class AlternativeItemTemplate : ItemContainerDecorator
{
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue( ItemTemplateProperty ); }
set { SetValue( ItemTemplateProperty, value ); }
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register( "ItemTemplate", typeof( DataTemplate ), typeof( AlternativeItemTemplate ), new PropertyMetadata( null ) );
public int Index
{
get { return (int) GetValue( IndexProperty ); }
set { SetValue( IndexProperty, value ); }
}
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register( "Index", typeof( int ), typeof( AlternativeItemTemplate ), new PropertyMetadata( -1 ) );
protected override void OnContainersChanged()
{
if (!AssociatedObject.Items.Any() || Index < 0 || Index >= AssociatedObject.Items.Count)
{
ItemContentPresenter = null;
ItemContentControl = null;
m_overwrittenTemplate = null;
return;
}
TryUpdateItem( ItemContainerGenerator.ContainerFromItem( AssociatedObject.Items[Index] ) );
}
private ContentPresenter ItemContentPresenter { get; set; }
private ContentControl ItemContentControl { get; set; }
private DataTemplate m_overwrittenTemplate;
private void TryUpdateItem( DependencyObject itemContainer )
{
if (itemContainer == null)
{
ResetOverwrittenTemplate();
}
var containerAsPresenter = itemContainer as ContentPresenter;
if (containerAsPresenter != null) UpdateItemContentPresenter( containerAsPresenter );
else
{
var containerAsControl = itemContainer as ContentControl;
if (containerAsControl != null) UpdateItemContentControl( containerAsControl );
}
}
private void ResetOverwrittenTemplate()
{
if (ItemContentPresenter != null)
ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;
if (ItemContentControl != null)
ItemContentControl.ContentTemplate = m_overwrittenTemplate;
ItemContentPresenter = null;
ItemContentControl = null;
m_overwrittenTemplate = null;
}
private void UpdateItemContentPresenter( ContentPresenter container )
{
if (ItemContentPresenter != null)
ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;
ItemContentPresenter = container;
m_overwrittenTemplate = ItemContentPresenter.ContentTemplate;
ItemContentPresenter.ContentTemplate = ItemTemplate;
}
private void UpdateItemContentControl( ContentControl container )
{
if (ItemContentControl != null)
ItemContentControl.ContentTemplate = m_overwrittenTemplate;
ItemContentControl = container;
m_overwrittenTemplate = ItemContentControl.ContentTemplate;
ItemContentControl.ContentTemplate = ItemTemplate;
}
}
public abstract class ItemContainerDecorator : Behavior<ItemsControl>
{
private Dictionary<object, DependencyObject> LastKnownContainers = new Dictionary<object, DependencyObject>();
protected ItemContainerGenerator ItemContainerGenerator { get { return (AssociatedObject != null) ? AssociatedObject.ItemContainerGenerator : null; } }
protected override void OnAttached()
{
base.OnAttached();
ItemContainerGenerator.ItemsChanged += HandleItemsChangedInitially;
if (!TryAddObservers())
{
AssociatedObject.Loaded += AddObserversOnLoaded;
}
AssociatedObject.Loaded += OnItemsControlLoaded;
AssociatedObject.LayoutUpdated += OnItemsControlLayoutUpdated;
CheckContainersChanged();
}
private void OnItemsControlLayoutUpdated(object sender, EventArgs eventArgs)
{
CheckContainersChanged();
}
private void OnItemsControlLoaded(object sender, RoutedEventArgs e)
{
CheckContainersChanged();
}
private void AddObserversOnLoaded( object sender, RoutedEventArgs e )
{
AssociatedObject.Loaded -= AddObserversOnLoaded;
TryAddObservers();
}
private bool TryAddObservers()
{
const bool success = true;
Panel itemsHost =
AssociatedObject.GetVisualDescendants().OfType<Panel>().FirstOrDefault( panel => panel.IsItemsHost );
if (itemsHost != null)
{
var virtualizingItemsHost = itemsHost as VirtualizingPanel;
if (virtualizingItemsHost != null)
{
virtualizingItemsHost.LayoutUpdated += OnVirtualizingItemsHostLayoutUpdated;
m_virtualizingItemsHost = virtualizingItemsHost;
}
return success;
}
return !success;
}
private VirtualizingPanel m_virtualizingItemsHost;
private bool LayoutUpdatedOccurredFirst;
private void OnVirtualizingItemsHostLayoutUpdated( object sender, EventArgs eventArgs )
{
LayoutUpdatedOccurredFirst = true;
CheckContainersChanged();
}
protected override void OnDetaching()
{
ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;
ItemContainerGenerator.ItemsChanged -= HandleItemsChanged;
AssociatedObject.Loaded -= OnItemsControlLoaded;
AssociatedObject.LayoutUpdated -= OnItemsControlLayoutUpdated;
AssociatedObject.Loaded -= AddObserversOnLoaded;
if (m_virtualizingItemsHost != null) m_virtualizingItemsHost.LayoutUpdated -= OnVirtualizingItemsHostLayoutUpdated;
m_virtualizingItemsHost = null;
base.OnDetaching();
}
private void HandleItemsChangedInitially( object sender, ItemsChangedEventArgs e )
{
ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;
if (!LayoutUpdatedOccurredFirst)
{
//sometimes calling UpdateLayout throws an ArgumentException
//don't know why so we just swallow it
//it's not particularly important
try
{
AssociatedObject.UpdateLayout();
}
catch (ArgumentException) { }
}
ItemContainerGenerator.ItemsChanged += HandleItemsChanged;
CheckContainersChanged();
}
private void HandleItemsChanged( object sender, ItemsChangedEventArgs e )
{
CheckContainersChanged();
}
private void CheckContainersChanged()
{
var newestContainers = new Dictionary<object, DependencyObject>();
foreach (var item in AssociatedObject.Items)
{
newestContainers[item] = ItemContainerGenerator.ContainerFromItem( item );
}
if (!LastKnownContainers.SequenceEqual( newestContainers ))
{
LastKnownContainers = newestContainers;
OnContainersChanged();
}
}
protected abstract void OnContainersChanged();
}
I have a ViewModel that sets the value for the "UserStructure" property. The problem is that the combobox wont bind to the value.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
/// <summary>
/// Load combobox structures
/// </summary>
private readonly LoadOperation<Structure> _loadStructures;
private readonly LoadOperation<UnitOccupierDetail> _loadUnitOccupierDetails;
//public ICommand SaveAccountSettingsCommand { get; set; }
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _userStructure;
public Structure UserStructure
{
get { return _userStructure; }
set
{
_userStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
private UnitOccupierDetail _unitOccupierDetail;
public UnitOccupierDetail UnitOccupierDetail
{
get { return _unitOccupierDetail; }
set
{
_unitOccupierDetail = value;
RaisePropertyChanged("UnitOccupierDetail");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
// SaveAccountSettingsCommand = new DelegateCommand(SaveAccountSettings, CanSave);
UserAccountContext _userAccountContext;
if (!DesignerProperties.IsInDesignTool)
{
var loggedInUser = new Guid(WebContext.Current.User.UserID.ToString());
_userAccountContext = new UserAccountContext();
#region load structures
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += _loadStructuresCompleted;
#endregion
#region load user data
_loadUnitOccupierDetails =
_userAccountContext.Load(
_userAccountContext.GetUnitOccupierDetailsQuery().Where(
u => u.UserIDFK == loggedInUser && u.StructureFK == 92));
_loadUnitOccupierDetails.Completed += _loadUnitOccupierDetails_Completed;
#endregion
}
}
void _loadUnitOccupierDetails_Completed(object sender, EventArgs e)
{
_unitOccupierDetail= new UnitOccupierDetail();
_unitOccupierDetail = _loadUnitOccupierDetails.Entities.First();
_userStructure = _unitOccupierDetail.Structure;
}
void _loadStructuresCompleted(object sender, EventArgs e)
{
var theseStructures = new ObservableCollection<Structure>(_loadStructures.Entities);
Structures = theseStructures;
}
//private void SaveAccountSettings(object param)
//{
//}
//private static bool CanSave(object param)
//{
// return true;
//}
}
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
DisplayMemberPath='StructureName'
SelectedValuePath='IDStructure'
SelectedItem='{Binding SelectedStructure,Mode=TwoWay}'
Width="200"
Height="25">
in xaml UserStructure instead SelectedStructure.
In your XAML SelectedItem is bound to SelectedStructure instead of UserStructure what you want
UPDATE:
Your code doesn't work because you should set to SelectedItem object that has reference equality with some object in ItemsSource. In your ViewModel you set Structures as result of one service operation and UserStructure as result of the other. UserStructure and some object in Structures can be equals (object.Equals) but not reference equals (object.ReferenceEquals). ComboBox like other ItemsControls doesn't compare items by equality, it compares they by identity. So, to have right code you should select from Structures object that equals to UserStructure and set it as UserStructure:
void _loadUnitOccupierDetails_Completed(object sender, EventArgs e)
{
...
Structure userStructure = Structures.FirstOrDefault(s=>s.Equals(_unitOccupierDetail.Structure));
UserStructure = userStructure;
}
In this case you should be sure that Structures comes before. You can look at Reactive Extensions Observable.ForkJoin() method to syncronize 2 async calls.
Try such changes
public class OwnerOccupierAccountViewModel : ViewModelBase
{
/// <summary>
/// Load combobox structures
/// </summary>
private readonly LoadOperation<Structure> _loadStructures;
private readonly LoadOperation<UnitOccupierDetail> _loadUnitOccupierDetails;
//public ICommand SaveAccountSettingsCommand { get; set; }
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _userStructure;
public Structure UserStructure
{
get { return _userStructure; }
set
{
_userStructure = value;
RaisePropertyChanged("UserStructure");
}
}
private UnitOccupierDetail _unitOccupierDetail;
public UnitOccupierDetail UnitOccupierDetail
{
get { return _unitOccupierDetail; }
set
{
_unitOccupierDetail = value;
RaisePropertyChanged("UnitOccupierDetail");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
// SaveAccountSettingsCommand = new DelegateCommand(SaveAccountSettings, CanSave);
UserAccountContext _userAccountContext;
if (!DesignerProperties.IsInDesignTool)
{
var loggedInUser = new Guid(WebContext.Current.User.UserID.ToString());
_userAccountContext = new UserAccountContext();
#region load structures
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += _loadStructuresCompleted;
#endregion
#region load user data
_loadUnitOccupierDetails =
_userAccountContext.Load(
_userAccountContext.GetUnitOccupierDetailsQuery().Where(
u => u.UserIDFK == loggedInUser && u.StructureFK == 92));
_loadUnitOccupierDetails.Completed += _loadUnitOccupierDetails_Completed;
#endregion
}
}
void _loadUnitOccupierDetails_Completed(object sender, EventArgs e)
{
_unitOccupierDetail= new UnitOccupierDetail();
_unitOccupierDetail = _loadUnitOccupierDetails.Entities.First();
_userStructure = _unitOccupierDetail.Structure;
}
void _loadStructuresCompleted(object sender, EventArgs e)
{
var theseStructures = new ObservableCollection<Structure>(_loadStructures.Entities);
Structures = theseStructures;
}
//private void SaveAccountSettings(object param)
//{
//}
//private static bool CanSave(object param)
//{
// return true;
//}
}
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
DisplayMemberPath='StructureName'
SelectedValuePath='IDStructure'
SelectedItem='{Binding UserStructure,Mode=TwoWay}'
Width="200"
Height="25">
Updated:
Hmm, maybe try changes here:
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem='{Binding UserStructure, Mode=TwoWay}'
Width="200"
Height="25">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=StructureName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Is there a way to add a button control to a cell in inside a ListView in a WinForms app?
Here is a code of a class ListViewExtender that you can reuse. It's not a derived class of ListView, basically you just declare that a specific column is displayed as buttons instead of text. The button's text is the subItem's text.
It allows big sized list views without problems, does not use p/invoke, and also works with horizontal scrollbars (some code proposed as answers here don't or are quite slow with a great number of items). Note it requires the extended ListView to have FullRowSelect set to true and view type set to Details.
This is a sample code that uses it:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent(); // you need to add a listView named listView1 with the designer
listView1.FullRowSelect = true;
ListViewExtender extender = new ListViewExtender(listView1);
// extend 2nd column
ListViewButtonColumn buttonAction = new ListViewButtonColumn(1);
buttonAction.Click += OnButtonActionClick;
buttonAction.FixedWidth = true;
extender.AddColumn(buttonAction);
for (int i = 0; i < 10000; i++)
{
ListViewItem item = listView1.Items.Add("item" + i);
item.SubItems.Add("button " + i);
}
}
private void OnButtonActionClick(object sender, ListViewColumnMouseEventArgs e)
{
MessageBox.Show(this, #"you clicked " + e.SubItem.Text);
}
}
}
Here is the ListViewExtender code and associated classes:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace WindowsFormsApplication1
{
public class ListViewExtender : IDisposable
{
private readonly Dictionary<int, ListViewColumn> _columns = new Dictionary<int, ListViewColumn>();
public ListViewExtender(ListView listView)
{
if (listView == null)
throw new ArgumentNullException("listView");
if (listView.View != View.Details)
throw new ArgumentException(null, "listView");
ListView = listView;
ListView.OwnerDraw = true;
ListView.DrawItem += OnDrawItem;
ListView.DrawSubItem += OnDrawSubItem;
ListView.DrawColumnHeader += OnDrawColumnHeader;
ListView.MouseMove += OnMouseMove;
ListView.MouseClick += OnMouseClick;
Font = new Font(ListView.Font.FontFamily, ListView.Font.Size - 2);
}
public virtual Font Font { get; private set; }
public ListView ListView { get; private set; }
protected virtual void OnMouseClick(object sender, MouseEventArgs e)
{
ListViewItem item;
ListViewItem.ListViewSubItem sub;
ListViewColumn column = GetColumnAt(e.X, e.Y, out item, out sub);
if (column != null)
{
column.MouseClick(e, item, sub);
}
}
public ListViewColumn GetColumnAt(int x, int y, out ListViewItem item, out ListViewItem.ListViewSubItem subItem)
{
subItem = null;
item = ListView.GetItemAt(x, y);
if (item == null)
return null;
subItem = item.GetSubItemAt(x, y);
if (subItem == null)
return null;
for (int i = 0; i < item.SubItems.Count; i++)
{
if (item.SubItems[i] == subItem)
return GetColumn(i);
}
return null;
}
protected virtual void OnMouseMove(object sender, MouseEventArgs e)
{
ListViewItem item;
ListViewItem.ListViewSubItem sub;
ListViewColumn column = GetColumnAt(e.X, e.Y, out item, out sub);
if (column != null)
{
column.Invalidate(item, sub);
return;
}
if (item != null)
{
ListView.Invalidate(item.Bounds);
}
}
protected virtual void OnDrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
protected virtual void OnDrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
ListViewColumn column = GetColumn(e.ColumnIndex);
if (column == null)
{
e.DrawDefault = true;
return;
}
column.Draw(e);
}
protected virtual void OnDrawItem(object sender, DrawListViewItemEventArgs e)
{
// do nothing
}
public void AddColumn(ListViewColumn column)
{
if (column == null)
throw new ArgumentNullException("column");
column.Extender = this;
_columns[column.ColumnIndex] = column;
}
public ListViewColumn GetColumn(int index)
{
ListViewColumn column;
return _columns.TryGetValue(index, out column) ? column : null;
}
public IEnumerable<ListViewColumn> Columns
{
get
{
return _columns.Values;
}
}
public virtual void Dispose()
{
if (Font != null)
{
Font.Dispose();
Font = null;
}
}
}
public abstract class ListViewColumn
{
public event EventHandler<ListViewColumnMouseEventArgs> Click;
protected ListViewColumn(int columnIndex)
{
if (columnIndex < 0)
throw new ArgumentException(null, "columnIndex");
ColumnIndex = columnIndex;
}
public virtual ListViewExtender Extender { get; protected internal set; }
public int ColumnIndex { get; private set; }
public virtual Font Font
{
get
{
return Extender == null ? null : Extender.Font;
}
}
public ListView ListView
{
get
{
return Extender == null ? null : Extender.ListView;
}
}
public abstract void Draw(DrawListViewSubItemEventArgs e);
public virtual void MouseClick(MouseEventArgs e, ListViewItem item, ListViewItem.ListViewSubItem subItem)
{
if (Click != null)
{
Click(this, new ListViewColumnMouseEventArgs(e, item, subItem));
}
}
public virtual void Invalidate(ListViewItem item, ListViewItem.ListViewSubItem subItem)
{
if (Extender != null)
{
Extender.ListView.Invalidate(subItem.Bounds);
}
}
}
public class ListViewColumnMouseEventArgs : MouseEventArgs
{
public ListViewColumnMouseEventArgs(MouseEventArgs e, ListViewItem item, ListViewItem.ListViewSubItem subItem)
: base(e.Button, e.Clicks, e.X, e.Y, e.Delta)
{
Item = item;
SubItem = subItem;
}
public ListViewItem Item { get; private set; }
public ListViewItem.ListViewSubItem SubItem { get; private set; }
}
public class ListViewButtonColumn : ListViewColumn
{
private Rectangle _hot = Rectangle.Empty;
public ListViewButtonColumn(int columnIndex)
: base(columnIndex)
{
}
public bool FixedWidth { get; set; }
public bool DrawIfEmpty { get; set; }
public override ListViewExtender Extender
{
get
{
return base.Extender;
}
protected internal set
{
base.Extender = value;
if (FixedWidth)
{
base.Extender.ListView.ColumnWidthChanging += OnColumnWidthChanging;
}
}
}
protected virtual void OnColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
{
if (e.ColumnIndex == ColumnIndex)
{
e.Cancel = true;
e.NewWidth = ListView.Columns[e.ColumnIndex].Width;
}
}
public override void Draw(DrawListViewSubItemEventArgs e)
{
if (_hot != Rectangle.Empty)
{
if (_hot != e.Bounds)
{
ListView.Invalidate(_hot);
_hot = Rectangle.Empty;
}
}
if ((!DrawIfEmpty) && (string.IsNullOrEmpty(e.SubItem.Text)))
return;
Point mouse = e.Item.ListView.PointToClient(Control.MousePosition);
if ((ListView.GetItemAt(mouse.X, mouse.Y) == e.Item) && (e.Item.GetSubItemAt(mouse.X, mouse.Y) == e.SubItem))
{
ButtonRenderer.DrawButton(e.Graphics, e.Bounds, e.SubItem.Text, Font, true, PushButtonState.Hot);
_hot = e.Bounds;
}
else
{
ButtonRenderer.DrawButton(e.Graphics, e.Bounds, e.SubItem.Text, Font, false, PushButtonState.Default);
}
}
}
}
The ListView itself (or ListViewItem) does not function as a container of any kind so no way to add controls directly, however it is doable. I have used this extended ListView with a lot of success: Embedding Controls in a ListView.
This is the BEST custom listview control for WinForms.
ObjectListView
To make the extender of Simon Mourier working is missing the following line:
extender.AddColumn(buttonAction);
This is, it should look like:
ListViewExtender extender = new ListViewExtender(listSummary);
ListViewButtonColumn buttonAction = new ListViewButtonColumn(2);
buttonAction.Click += OnButtonActionClick;
buttonAction.FixedWidth = true;
extender.AddColumn(buttonAction);
Maybe this could be of interest?
http://www.codeproject.com/KB/list/extendedlistviews.aspx
No, a standard Windows Forms ListView doesn't support embedded controls. You could try to build your own custom control, or you could use something like http://www.codeproject.com/KB/list/EXListView.aspx.
No and yes, ListView itself does not support such functionality, but you can create a button on top of it, so that it appears to the user as integral part of the listview. (I suppose this is what the ExtendedListView mentioned above does too).
Maybe it worths mentioning, the list view control might be designed in WPF as an usercontrol/custom control with buttons in its ListViewItems, and then use this control in the WinForms application, in an ElementHost control.
I accidentally come across a discussion before, hope this help: http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/ee232cc4-68c5-4ed3-9ea7-d4d999956504/
You could use a GlacialList. It allow you to put ANY control inside a list cell and it's simple to use. You will just need to join a GlacialList.dll document to the reference part of your Solution. If you click the link it will show you how it works and how to use it and download it.
If you have a System.IO.FileNotFoundException on the InitializeComponent() just download source code from the above link, compile and use this .dll (inside bin/Debug subfolder) to your project .
Here is an example of what it looks like:
This looks like the simplest answer I have come across... just added an ItemCommand to the ListView.
See this link: handle-the-button-click-event-from-an-asp-net-listview-control