Prevent showing details when hyperlink is clicked - c#

I'm wondering how to prevent showing details row in DataGrid, when somebody clicks on hyperlink or button inside a cell. It's really annoying when you try to click hyperlink and details show instead of link.
Another problem is that I have some action buttons in one column, so when details are collapsed then you must first click row to show details and then click for example edit button.
Sample:
MainWindow.xaml.cs
namespace WpfApplication1
{
public class Item
{
public string Column0 { get; set; }
public string Mail { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Item> Items
{
get
{
ObservableCollection<Item> i = new ObservableCollection<Item>();
i.Add(new Item() { Column0 = "dsaads", Mail = "mail#sad.com" });
i.Add(new Item() { Column0 = "wdads", Mail = "adsdas#sad.com" });
return i;
}
}
public void HyperlinkClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Clicked");
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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>
<DataGrid RowDetailsVisibilityMode="VisibleWhenSelected" ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Column0" Binding="{Binding Column0}" />
<DataGridHyperlinkColumn Header="Mail" Width="*" Binding="{Binding Mail}" >
<DataGridHyperlinkColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="Padding" Value="2,0,2,0" />
<EventSetter Event="Hyperlink.Click" Handler="HyperlinkClick" />
</Style>
</DataGridHyperlinkColumn.ElementStyle>
</DataGridHyperlinkColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid Height="100">
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
To see the problem: select first row and then try to click on hyperlink in a second row.

You can handle the tunneling event on hyperlink "OnPreviewMouseDown", that will prevent the event reaching the DataGrid which shows the RowDetailsTemplate.
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var hyperlink = (Hyperlink)sender;
Process.Start(hyperlink.NavigateUri.AbsoluteUri);
e.Handled = true;
}
Full Example:
<Window x:Class="DummyTree.DataGridTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="DataGridTest" Height="300" Width="300">
<Grid>
<DataGrid ItemsSource="{Binding Customers}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink PreviewMouseDown="OnPreviewMouseDown" NavigateUri="http://www.google.com">
<TextBlock Text="{Binding Name}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" details here" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
Code Behind:
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace DummyTree
{
public partial class DataGridTest : Window
{
public DataGridTest()
{
DataContext = new CustomerVM();
InitializeComponent();
}
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var hyperlink = (Hyperlink)sender;
Process.Start(hyperlink.NavigateUri.AbsoluteUri);
e.Handled = true;
}
}
public class CustomerVM
{
public ObservableCollection<Customer> Customers { get; set; }
public CustomerVM()
{
Customers = new ObservableCollection<Customer> { new Customer { Name = "Leo" }, new Customer { Name = "Om" } };
}
}
public class Customer
{
public string Name { get; set; }
}
}

Related

Checkbox as part of dataGrid - how to call event on bound data in the right order

I am trying to perform a calculation based on checkbox value in a datagrid as soon as a checkbox value changes, but am struggling. I thing part of the problem is timing of events.
The datagrid is bound to an observable collection. I had throught the values would update instantly, but seems not.
Below is the xaml:
<Window x:Class="GridViewTest.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:GridViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<GroupBox Header="Data" Grid.Row ="1" Margin="0,0,0,0" DockPanel.Dock="Top" Height="100">
<DataGrid x:Name="dataGrid" Margin="0,0,0,0" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserSortColumns="False"
CanUserResizeColumns="False" CanUserReorderColumns="False" VerticalAlignment="Stretch" >
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Flag" Binding="{Binding Path=flag}" Width="35" IsReadOnly="false">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="UseCheckboxChanged"/>
<EventSetter Event="CheckBox.Unchecked" Handler="UseCheckboxChanged"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
<DataGridTextColumn Header="Value" Binding="{Binding Path=dataValue, StringFormat=N3}" Width="50" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<Button x:Name="myButton" Content="Count" DockPanel.Dock="Top" Height="100" Click="myButton_Click"/>
<TextBox x:Name="myTextBox" DockPanel.Dock="Top" Height="25"/>
</DockPanel>
</Window>
And the code:
namespace GridViewTest
{
public class Data
{
private bool _flag;
public bool flag { get; set; }
public double dataValue { get; set; }
public Data()
{
flag = false;
dataValue = 0;
}
}
public partial class MainWindow : Window
{
ObservableCollection<Data> dataCol = new ObservableCollection<Data>();
public MainWindow()
{
InitializeComponent();
dataCol = new ObservableCollection<Data>();
Data dataPt1 = new Data();
dataPt1.flag = false;
dataPt1.dataValue = 1.23;
Data dataPt2 = new Data();
dataPt2.flag = true;
dataPt2.dataValue = 2.34;
dataCol.Add(dataPt1);
dataCol.Add(dataPt2);
dataGrid.ItemsSource = dataCol;
}
private void UseCheckboxChanged(object sender, RoutedEventArgs e)
{
performCount();
}
public void performCount()
{
int count = 0;
double sum = 0;
foreach (Data dataPt in dataCol)
{
if (dataPt.flag)
{
count++;
sum += dataPt.dataValue;
}
}
this.myTextBox.Text = count.ToString() + "|" + sum.ToString();
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
performCount();
}
}
}
My function "performCount" is called on the "CheckBox.Checked" and "CheckBox.UnChecked" but it uses the old value, so when I run the code for the first time, and check/uncheck a box, the value in the textbox does not change. Then if I click on checkbox again the value displayed is what it previously should have been.
So the event seems to happen before the ObservableCollection is updated.
If I click the button it displays the right value.
What is the correct way to ahve the observable collection update before running the calculation?
add UpdateSourceTrigger=PropertyChanged to DataGridCheckBoxColumn binding:
<DataGridCheckBoxColumn
Header="Flag"
Binding="{Binding Path=flag, UpdateSourceTrigger=PropertyChanged}"
Width="35"
IsReadOnly="false">

How to refresh a DataGrid that has been populated by a DataProvider every 1min

I have the following xaml, as you can see the DataGrids are being populated via DataProviders.
<Window x:Class="MobileDeviceAuthenticator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MobileDeviceAuthenticator"
Title="Device Authorization" Height="381" Width="879" AllowDrop="True">
<Window.Resources>
<!-- create an instance of our DataProvider class -->
<ObjectDataProvider x:Key="MobileManagerDataProvider" ObjectType="{x:Type local:MobileManagerDataProvider}"/>
<!-- define the method which is invoked to obtain our data -->
<ObjectDataProvider x:Key="MOBILE_MANAGER" ObjectInstance="{StaticResource MobileManagerDataProvider}" MethodName="GetDevices"/>
<!-- create an instance of our DataProvider class -->
<ObjectDataProvider x:Key="MobileRequestDataProvider" ObjectType="{x:Type local:MobileRequestDataProvider}"/>
<!-- define the method which is invoked to obtain our data -->
<ObjectDataProvider x:Key="MOBILE_REQUESTS" ObjectInstance="{StaticResource MobileRequestDataProvider}" MethodName="GetDevices"/>
</Window.Resources>
<Grid Name="GridContainer" >
<Grid>
<DataGrid Name="dgAuthorization"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DataContext="{Binding Source={StaticResource MOBILE_MANAGER}}"
ItemsSource="{Binding}"
AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DESCRIPTION}" Header="Description" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_TYPE}" Header="Device Type" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_ID}" Header="Device ID" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
</Grid>
<Grid>
<DataGrid Name="dgRequest"
SelectionMode="Single"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DataContext="{Binding Source={StaticResource MOBILE_REQUESTS}}"
ItemsSource="{Binding}"
AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DESCRIPTION}" Header="Description" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_TYPE}" Header="Device Type" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_ID}" Header="Device ID" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=REQUEST_DATE}" Header="Request Date" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
Here is the DataProvider class code
public class MobileManagerDataProvider
{
private MobileManagerDataSetTableAdapters.MOBILE_MANAGERTableAdapter mmAdapter;
private MobileManagerDataSet mmDataSet;
public MobileManagerDataProvider()
{
mmDataSet = new MobileManagerDataSet();
mmAdapter = new MobileManagerDataSetTableAdapters.MOBILE_MANAGERTableAdapter();
mmAdapter.Fill(mmDataSet.MOBILE_MANAGER);
mmDataSet.MOBILE_MANAGER.MOBILE_MANAGERRowChanged += new MobileManagerDataSet.MOBILE_MANAGERRowChangeEventHandler(AuthenticationRowModified);
mmDataSet.MOBILE_MANAGER.MOBILE_MANAGERRowDeleted += new MobileManagerDataSet.MOBILE_MANAGERRowChangeEventHandler(AuthenticationRowModified);
}
public DataView GetDevices()
{
return mmDataSet.MOBILE_MANAGER.DefaultView;
}
void AuthenticationRowModified(object sender, MobileManagerDataSet.MOBILE_MANAGERRowChangeEvent e)
{
mmAdapter.Update(mmDataSet.MOBILE_MANAGER);
}
}
In the code-behind I'd like to setup a Timer ticking away every minute refreshing the DataGrids.
The Timer is easy enough, however to refresh the data is eluding me. I've tried some of the following statements.
ObjectDataProvider dataProvider = this.TryFindResource("MobileManagerDataProvider") as ObjectDataProvider;
dataProvider.Refresh();
dgAuthorization.Items.Refresh();
CollectionViewSource.GetDefaultView(dgAuthorization.ItemsSource).Refresh();
To no avail, how can I achieve this?
this is a complete working example, every time you select an item (not same item twice) it will add a new random value as a new line in the DataGrid
I've responded with an example binding new data to the DataGrid every time you select an item in it, you can expand that case, so each time your collection changes, it will reflect in your DataGrid automatically, so whenever your data changes you just pass to the GridData property they will be updated in the view
MainWindow.xaml
<Window x:Class="BindingDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingDataGrid"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<DataGrid ColumnWidth="*"
IsReadOnly="True"
AutoGenerateColumns="False"
SelectionMode="Single"
HorizontalContentAlignment="Center"
ItemsSource="{Binding GridData}"
SelectedItem="{Binding SelectedData, UpdateSourceTrigger=PropertyChanged}"
CanUserResizeRows="False"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="Column 01" Binding="{Binding Column1}" Width="*"/>
<DataGridTextColumn Header="Column 02" Binding="{Binding Column2}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs Nothing here, just remove unused references
using System.Windows;
namespace BindingDataGrid //`BindingDataGrid` is my namespace
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
all references you need in your MainViewModel
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
MainViewModel
public class MainViewModel: INotifyPropertyChanged
{
Random _rnd = new Random();
private MyGridData _selectedData;
private ObservableCollection<MyGridData> _gridData;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel()
{
var newData = new List<MyGridData>
{
new MyGridData { Column1 = "AAAAAA 01", Column2 = "aaaaaaaa 02" },
new MyGridData { Column1 = "BBBBBB 01", Column2 = "bbbbbbbb 02" },
new MyGridData { Column1 = "CCCCCC 01", Column2 = "cccccccc 02" },
};
GridData = new ObservableCollection<MyGridData>(newData);
}
public MyGridData SelectedData
{
get { return _selectedData; }
set
{
if (value == _selectedData) //if same item selected
return;
_selectedData = value;
OnPropertyChanged();
DoSomethingWhenValueChanged(); // Will add a new line
}
}
public ObservableCollection<MyGridData> GridData
{
get { return _gridData; }
set
{
if (value == _gridData)
return;
_gridData = value;
OnPropertyChanged();
}
}
private void DoSomethingWhenValueChanged()
{
var newData = GridData.ToList();
newData.Add(new MyGridData { Column1 = (_rnd.Next(100)).ToString(), Column2 = (_rnd.Next(1000)).ToString() }); //Add new random item
GridData = new ObservableCollection<MyGridData>(newData); //update your DataGrid
}
}
public class MyGridData
{
public string Column1 { get; set; }
public string Column2 { get; set; }
}

Multiselect Listview with select all check box. Datatrigger on binded property

I have a list view with a grid view that has two columns. The first column contains checkboxes that are bound to the listviewitem selected property, the second column is text. In the header for the checkboxes column I have a check box that I want to function as a select / deselect all button. I have used datatriggers to do this but it only works when I remove the binding between the checkboxes and the selected property. Should I be able to use a data trigger to set a bound property?
Screenshot
codebehind
namespace ListviewWCheckboxes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
List<string> listItems = new List<string>() { "foo", "bar", "blah" };
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
pdflistView.ItemsSource = listItems;
}
}
}
xaml
<Window x:Class="ListviewWCheckboxes.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:ListviewWCheckboxes"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Grid>
<ListView x:Name="pdflistView" HorizontalAlignment="Left" Height="300" Margin="5" VerticalAlignment="Top" Width="240"
SelectionMode="Extended"
>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.Header>
<CheckBox x:Name="ckbxSelectAll"/>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}, Mode=FindAncestor},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ckbxSelectAll, Path=IsChecked}" Value="True">
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ckbxSelectAll, Path=IsChecked}" Value="False">
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<DataTemplate.Triggers>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Pdf" DisplayMemberBinding="{Binding}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Using AjS's suggestion I came up with this solution.
I was also able to come up with a solution that drilled down the visual tree of the listbox till I got the VirtualizingStackPanel that contains all of the ListviewItems and loop through them setting the IsSelected property but this seems cleaner.
xaml
<Window x:Class="ListviewWCheckboxes.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:ListviewWCheckboxes"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView x:Name="pdflistView" HorizontalAlignment="Left" Height="300" Margin="5" VerticalAlignment="Top" Width="240"
SelectionMode="Extended" ItemsSource="{Binding Path=listItems}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding is_Selected}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.Header>
<CheckBox x:Name="ckbxSelectAll" Checked="ckbxSelectAll_Checked" Unchecked="ckbxSelectAll_Checked"/>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding is_Selected}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Pdf" DisplayMemberBinding="{Binding Path=text}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
code
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace ListviewWCheckboxes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<someobjects> listItems { get; set; } = new ObservableCollection<someobjects>();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
listItems.Add(new someobjects("foo"));
listItems.Add(new someobjects("bar"));
listItems.Add(new someobjects("blah"));
}
private void ckbxSelectAll_Checked(object sender, RoutedEventArgs e)
{
foreach (someobjects item in listItems)
{
item.is_Selected = (bool)((CheckBox)sender).IsChecked;
}
}
public class someobjects : INotifyPropertyChanged
{
private string _text;
public string text
{
get { return _text; }
set { _text = value; OnPropertyChanged(); }
}
private bool _is_Selected;
public bool is_Selected
{
get { return _is_Selected; }
set { _is_Selected = value; OnPropertyChanged(); }
}
public someobjects(string t)
{
text = t;
is_Selected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
for anyone wondering about the 2nd solution I mentioned, this event will have to be attached to the checked and unchecked events of the selectall checkbox
private void ckbxSelectAll_Checked(object sender, RoutedEventArgs e)
{
DockPanel dp = FindVisualChild<DockPanel>(pdflistView, 0);
ScrollContentPresenter scp = FindVisualChild<ScrollContentPresenter>(dp, 1);
VirtualizingStackPanel vsp = FindVisualChild<VirtualizingStackPanel>(scp, 0);
foreach (ListViewItem item in vsp.Children)
{
item.IsSelected = (bool)((CheckBox)sender).IsChecked;
}
}
private static T FindVisualChild<T>(UIElement element, int childindex) where T : UIElement
{
UIElement child = element;
while (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
child = VisualTreeHelper.GetChild(child, childindex) as UIElement;
}
return null;
}
I believe the best way to implement this is to create a property 'IsSelected' on your class and bound that to the checkbox (IsChecked) and listview (isSelected). Handle the checked event of the header checkbox and manually toggle the IsSelected property of the items in the collection so that checkbox and listviewitems are updated. Please let me know if you have any other questions

WPF Add Header to Custom ComboBox

I have a custom ComboBox that have each item (Favorites and not favorites) is a Label + Button, then the last item have only a button to load all elements. Now I want to add a header as the first item, that says "Favorites".
Right now I have:
<ComboBox
x:Name="ComboBoxBtn"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,0,0,-1"
Width="300"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Path=Selected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Name="PART_GRID">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Width="250" Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}" />
<Button Name="PART_BUTTON"
Grid.Column="1"
Content="+"
Command="{Binding AddCommandButton, ElementName=root}"
CommandParameter="{Binding}"
Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}"/>
<Button Content="Carregar Todos" Margin="5,5"
Command="{Binding LoadAllCommandButton, ElementName=root}"
CommandParameter="{Binding ElementName=root, Path=FavoriteType}"
Visibility="{Binding Path=.,Converter={StaticResource elementToVisibilityForAddConverter}}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Favorite}"
Value="True">
<Setter TargetName="PART_GRID"
Property="Background"
Value="#FFE6E6FA" />
<Setter TargetName="PART_BUTTON"
Property="Content"
Value="-" />
<Setter TargetName="PART_BUTTON"
Property="Command"
Value="{Binding RemoveCommandButton, ElementName=root}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I preferred a different aproach, which i think is easier and more clean:
i created an empty interface IDrawable.
all classes i need to put inside the combobox should inherit from IDrawable
i created these:
MyLabel:
public class MyLabel : IDrawable
{
public string text { get; set; }
public MyLabel()
{
this.text = "MYTEXT";
}
}
MyButton:
public class MyButton : IDrawable
{
public string text { get; set; }
public MyButton()
{
this.text = "MYNBUTTON";
}
}
MyLabelButton:
public class MyLabelButton : IDrawable
{
public string labelText { get; set; }
public string buttonText { get; set; }
public MyLabelButton()
{
labelText = "labelText";
buttonText = "buttonText";
}
}
than here is the xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1">
<Window.Resources>
<DataTemplate DataType="{x:Type me:MyButton}">
<Button Content="{Binding text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabel}">
<TextBlock Text="{Binding text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabelButton}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding labelText}"/>
<Button Content="{Binding buttonText}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Name="MyGrid">
<ComboBox Name="MyCombo" ItemsSource="{Binding list}" SelectedItem="{Binding sel}" PreviewMouseLeftButtonUp="ComboBox_PreviewMouseLeftButtonUp"/>
</Grid>
</Window>
and codebehind:
public partial class MainWindow : Window
{
private IDrawable clicked;
public ObservableCollection<IDrawable> list { get; set; }
public IDrawable sel { get; set; }
public MainWindow()
{
InitializeComponent();
list = new ObservableCollection<IDrawable>();
list.Add(new MyLabel());
list.Add(new MyLabelButton());
list.Add(new MyButton());
this.DataContext = this;
}
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition(MyGrid);
clicked = null;
VisualTreeHelper.HitTest(
MyGrid,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
private HitTestResultBehavior ResultCallback(HitTestResult result)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(result.VisualHit);
if (parentObject == null)
return HitTestResultBehavior.Continue;
var v = parentObject as Button;
if (v == null)
return HitTestResultBehavior.Continue;
if (v.DataContext != null && v.DataContext is IDrawable)
{
clicked = (IDrawable)v.DataContext;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
}
the result is
as you can see I have standard combobox and custom elements. which i think it's better. in codebehind you can handle everything, like the impossibility of select the first label, call the command associated with the button, if the button is pressed, and so on.
In ComboBox_PreviewMouseLeftButtonUp i handled the click on the selected item, in case you want to do something particular if the selected button is pressed, and not show the dropdown menu.
this example is pretty barebones you need to customize it a little more and use MVVM everywhere.
at the moment you can push the button in the dropDown menu, you probabily want to disable the click if that buttont isn't selected.
EDIT
ComboBox_PreviewMouseLeftButtonUp should be like this:
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((ComboBox)sender);
clicked = null;
VisualTreeHelper.HitTest(
(ComboBox)sender,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
(replaced Mygrid with (ComboBox)sender

C# WPF - editable column in a datagrid

Problem Description
I have a DataGrid named data_grid_1, which has two columns header1 and header2 and is filled with SomeClass-object's inside the data_grid_1 Loaded-event-handler.
XAML-Code:
<Window x:Class="WpfApplication1.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>
<DataGrid x:Name="data_grid_1"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
Height="303" Width="497"
Loaded="data_grid_1_Loaded">
<DataGrid.Columns>
<DataGridTextColumn Header="header1"
Binding="{Binding some_field_variable_2, Mode=OneWay}" />
<DataGridTextColumn Header="header2"
Binding="{Binding some_field_variable_3, Mode=OneWay}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
XAML.CS-Code:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void data_grid_1_Loaded(object sender, RoutedEventArgs e)
{
data_grid_1.Items.Add(new SomeClass(1, "data_grid_1-String1", "data_grid_1-Text1"));
data_grid_1.Items.Add(new SomeClass(2, "data_grid_1-String2", "data_grid_1-Text2"));
data_grid_1.Items.Add(new SomeClass(3, "data_grid_1-String3", "data_grid_1-Text3"));
data_grid_1.Items.Add(new SomeClass(4, "data_grid_1-String4", "data_grid_1-Text4"));
}
}
}
SomeClass.cs
namespace WpfApplication1
{
class SomeClass
{
public int some_field_variable_1 { get; internal set; }
public string some_field_variable_2 { get; internal set; }
public string some_field_variable_3 { get; internal set; }
public SomeClass(int some_field_variable_1,
string some_field_variable_2,
string some_field_variable_3)
{
this.some_field_variable_1 = some_field_variable_1;
this.some_field_variable_2 = some_field_variable_2;
this.some_field_variable_3 = some_field_variable_3;
}
}
}
Question
I want to make the header2-column editable, so if the user clicks and changes the value of a row in header2-column the changes are written into the SomeClass-object, which were previously added inside data_grid_1_Loaded.
How can i make the second column editable?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static ObservableCollection<SomeClass> GetItems()
{
ObservableCollection<SomeClass> some_inner_object_list = new ObservableCollection<SomeClass>();
some_inner_object_list.Add(new SomeClass(1, "data_grid_1-String1", "data_grid_1-Text1"));
some_inner_object_list.Add(new SomeClass(2, "data_grid_1-String2", "data_grid_1-Text2"));
some_inner_object_list.Add(new SomeClass(3, "data_grid_1-String3", "data_grid_1-Text3"));
some_inner_object_list.Add(new SomeClass(4, "data_grid_1-String4", "data_grid_1-Text4"));
return some_inner_object_list;
}
private void data_grid_1_Loaded(object sender, RoutedEventArgs e)
{
data_grid_1.ItemsSource = GetItems();
}
private void data_grid_1_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
if (e.AddedCells.Count == 0) return;
var currentCell = e.AddedCells[0];
if (currentCell.Column ==
data_grid_1.Columns[1])
{
data_grid_1.BeginEdit();
}
}
}
XAML-Code:
<DataGrid x:Name="data_grid_1"
AutoGenerateColumns="False"
CanUserAddRows="False"
SelectionMode="Extended"
SelectionUnit="Cell"
Loaded="data_grid_1_Loaded"
SelectedCellsChanged="data_grid_1_SelectedCellsChanged" Margin="0,12,12,77">
<DataGrid.Columns>
<DataGridTextColumn Header="header1" Binding="{Binding some_field_variable_2, Mode=OneWay}" Width="1*" />
<DataGridTemplateColumn Header="header2" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding some_field_variable_3}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding some_field_variable_3}" />
<TextBlock Grid.Column="1" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
just using DataGridTextColumn.CellStyle
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<TextBox>//Binding your logic here
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
hope it helps..:)

Categories