WPF Add Header to Custom ComboBox - c#

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

Related

DataGrid is updating itself in realtime and holding unsaved changes

I have a problem with DataGrid and after week trying to solve this I am out of mind.
I am using Prism.MVVM to handle loading properties, INotifyPropertyChanged etc.
My datagrid is being populated from database (EF) by of course ViewModel. When I double click on the row edit window will open with populated fields etc. I am doing this by "SelectedItem". Everything to this moment is working fine, but:
• When I editing my "Stock" textbox I see in the ProductListView window that this value is changing in realtime and even if I hit Cancel (and the window closed) it stays as I left it in ProductView and even after opening edit window again "Stock" value remain wrong, but in database the value is correct.
• When I edit for example "Category" or "Name" I do not see changes in datagrid in realtime (in this case values in DataGrid stay correct), but if I hit Cancel (and the window closed) an reopen edit window again value remain wrong, but in database the value is correct.
I tried to DeepCopy it and then Override SelectedItem back after edit and it work for database (it getting updated), but view (DataGrid) does not.
ProductListView:
<DataGrid ColumnWidth="Auto" Grid.Row="1" ItemsSource="{Binding ProductsCollectionView, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False" IsReadOnly="True"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Auto">
Methods/Commands responsible for edit in ProductListViewModel:
private Product _selectedItem;
public Product SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ProductListViewModel() // Contructor
{
_service = new ProductService();
Load();
ProductsCollectionView = CollectionViewSource.GetDefaultView(Data);
ProductsCollectionView.Filter = FilterProducts;
LoadCommands();
EditCommand = new DelegateCommand(Edit);
}
public ICommand EditCommand { get; set; }
private void Edit()
{
var dialog = new ProductView(SelectedItem);
dialog.ShowDialog();
if (dialog.DialogResult == true)
{
_service.UpdateProductData(SelectedItem);
_service.SaveChanges();
}
}
ProductView (Btw. I am using same View for Adding and Editing products, that is why I passing "SeleectedItem through the constructor)
<StackPanel Margin="0,0,13,0" Grid.Column="0">
<TextBlock Text="Product type" Style="{StaticResource StackPanelTextBox}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="41*" />
<ColumnDefinition Width="251*"/>
<ColumnDefinition Width="31*" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True" IsTextSearchEnabled="True" ItemsSource="{Binding ProductTypes}" DisplayMemberPath="Name" SelectedValuePath="ProductTypeId" SelectedValue="{Binding Data.ProductTypeId, UpdateSourceTrigger=PropertyChanged}" Grid.ColumnSpan="2"/>
<Button Grid.Column="1" Command="{Binding AddProductTypeCommand}" FontSize="12" Padding="0" Height="15" Background="Transparent" Grid.ColumnSpan="2" Margin="251,6,0,6"/>
</Grid>
</StackPanel>
<StackPanel Margin="0,0,13,0" Grid.Column="1">
<TextBlock Text="Signature" Style="{StaticResource StackPanelTextBox}"/>
<TextBox Text="{Binding Data.Signature}"/>
</StackPanel>
<StackPanel Margin="0,0,0,0" Grid.Column="2">
<TextBlock Text="EAN" Style="{StaticResource StackPanelTextBox}"/>
<TextBox MaxLength="13" Text="{Binding Data.Ean}"/>
</StackPanel>
<Grid Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*" />
<ColumnDefinition Width="20*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0" Margin="0,7,13,10">
<TextBlock Text="Name" Style="{StaticResource StackPanelTextBox}"/>
<TextBox x:Name="tbName" Text="{Binding Data.Name}"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0" Margin="0,0,0,0" VerticalAlignment="Center">
<TextBlock Text="Stock"/>
<mah:NumericUpDown Margin="0,3,0,3" Minimum="0" Interval="1" Value="{Binding Data.InStock}" />
</StackPanel>
</Grid>
ProductView.xaml.cs
public ProductView(Product product)
{
InitializeComponent();
var viewModel = new ProductViewModel(product);
WindowStartupLocation = WindowStartupLocation.CenterScreen;
viewModel.SaveAction = () =>
{
DialogResult = true;
};
viewModel.CancelAction = () =>
{
DialogResult = false;
};
DataContext = viewModel;
}
ProductViewModel
public Action SaveAction { get; set; }
public Action CancelAction { get; set; }
public ICommand Save { get; set; }
public ICommand Cancel { get; set; }
private Product _data;
public Product Data
{
get => _data;
set => SetProperty(ref _data, value);
}
public ProductViewModel(Product data)
{
LoadSellers();
LoadProductTypes();
LoadPackages();
Data = data;
NettoPrice = Data.PurchasePrice;
Save = new DelegateCommand(() => SaveAction?.Invoke());
Cancel = new DelegateCommand(() => CancelAction?.Invoke());
HideData = new DelegateCommand(HideMethod);
AddProductTypeCommand = new DelegateCommand(AddProductType);
EyeColor = #"..\Resources\Images\Eye-grey-48.png";
}
You need an extra data layer if you want to implement edit and cancel behavior of a data model. You can do this by implementing the IEditableObject interface.
You should never open the dialog from the view model. Instead, open it from a Button.Click handler in the code-behind. Define a DataTemplate for the dialog and assign it to the Window.ContentTemplate property. Also make sure that the database handling is implemented inside the model.
The following example shows how to display a reusable EditDialog (that operates on IEditableObject implementations) from the view. The example also shows how to cancel or commit data changes to the data model.
App.xaml
<DataTemplate DataType="{x:Type local:Product}">
<TextBox Text="{Binding Signature}" />
</DataTemplate>
EditDialog.xaml
Reusable dialog. Simply define a DataTemplate for the Window.ContentTemplate property to change the hosted Content.
Assign the data to be edited to the EditDialog.DataContext. This dialog can host any data that implements IEditableObject.
<Window Content="{Binding}">
<Window.Template>
<ControlTemplate TargetType="Window">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<AdornerDecorator Grid.Row="0">
<ContentPresenter />
</AdornerDecorator>
<!-- Dialog chrome -->
<Grid Grid.Row="1">
<StackPanel Orientation="Horizontal">
<Button Content="Commit"
Click="OnOkButtonClicked" />
<Button Content="Cancel"
Click="OnCancelButtonClicked"
IsCancel="True"
IsDefault="True" />
</StackPanel>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode"
Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Template>
</Window>
EditDialog.xaml.cs
public partial class EditDialog : Window
{
public EditDialog()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
// In case the Window is closed using the chrome button
protected override void OnClosing(CancelEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.CancelEdit();
}
base.OnClosing(e);
}
private void OnLoaded(object sender, EventArgs e)
{
// Content is set via data binding
if (this.Content is IEditableObject editableObject)
{
editableObject.BeginEdit();
}
}
private void OnCancelButtonClicked(object sender, RoutedEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.CancelEdit();
}
this.Close();
}
private void OnOkButtonClicked(object sender, RoutedEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.EndEdit();
}
this.Close();
}
}
MainWindow.xaml
<DataGrid ItemsSource="{Binding ProductsCollectionView}"
SelectedItem="{Binding SelectedItem}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick"
Handler="DataGridRow_MouseDoubleClick" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProductListViewModel();
}
private void DataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var productListViewModel = this.DataContext as ProductListViewModel;
// Alternatively, create a e.g., EditItem dependency property
// and bind it to the DataGrid.SelectedItem
Product editItem = productListViewModel.SelectedItem;
var editDialog = new EditDialog()
{
DataContext = editItem
};
editDialog.ShowDialog();
}
}
Product.cs
class Product : INotifyPropertyChanged, IEditableObject
{
internal class ProductData
{
// Use object.MemberwiseClone to create a shallow copy
public ProductData Clone() => MemberwiseClone() as ProductData;
public string Signature { get; set; }
}
public Product()
{
this.EditData = new ProductData();
this.BackupData = new ProductData();
}
public void BeginEdit()
{
if (this.IsInEditMode)
{
// Consider to throw an exception
return;
}
// Creates a shallow copy.
// If required, use a copy constructor to create a deep copy.
this.BackupData = this.EditData.Clone();
this.IsInEditMode = true;
}
public void CancelEdit()
{
if (!this.IsInEditMode)
{
// Consider to throw an exception
return;
}
this.EditData = this.BackupData;
this.IsInEditMode = false;
// Raise change notification for all public properties
// to undo potential binding changes
OnPropertyChanged(null);
}
public void EndEdit()
{
this.IsInEditMode = false;
}
public string Signature
{
get => this.EditData.Signature;
set
{
this.EditData.Signature = value;
OnPropertyChanged();
}
}
public bool IsInEditMode { get; private set; }
private ProductData BackupData { get; set; }
private ProductData EditData { get; set; }
}

How to add grid to my combobox and display obervableCollection data?

I use a combobox and I would like to proceed as follows:
I choose an element in cmb1, this allows to display in cmb2 a Collection.
This code allows me to retrieve the data I want ( result A = ObservableCollectionA, result B = ObservableCollection B...)
<ComboBox Name="cmbResultatListe"
Margin="0,10,0,10"
Grid.Row="4"
Grid.Column="2"
Height="25"
Width="250" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Etablissement">
<Setter Property="ItemsSource" Value="{Binding Etablissements}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Service">
<Setter Property="ItemsSource" Value="{Binding Services}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
I would now like to divide my combobox into three grids, so that I can proceed as follows:
If result A is selected => cmb2 grid0 = ObservableCollectionA.ID, cmb2 grid1 = observableCollectionA.Name...
If result B is selected => cmb2 grid0 = ObservableCollectionB.Name, cmb2 grid1 = observableCollectionB.Years...
And i don't know how can i do that.
Any tips ?
Thank you for your help.
Edit :
c# code :
private ObservableCollection<Etablissement> _EtablissementsUtilisateur;
public ObservableCollection<Etablissement> EtablissementsUtilisateur
{
get
{
return _EtablissementsUtilisateur;
}
set
{
if (value != _EtablissementsUtilisateur)
{
_EtablissementsUtilisateur = value;
RaisePropertyChanged(nameof(EtablissementsUtilisateur));
}
}
}
private ObservableCollection<ServiceSectionModel> _Services;
public ObservableCollection<ServiceSectionModel> Services
{
get
{
return _Services;
}
set
{
if (value != _Services)
{
_Services = value;
RaisePropertyChanged(nameof(Services));
}
}
}
private ObservableCollection<ServiceSectionModel> _Sections;
public ObservableCollection<ServiceSectionModel> Sections
{
get
{
return _Sections;
}
set
{
if (value != _Sections)
{
_Sections = value;
RaisePropertyChanged(nameof(Sections));
}
}
}
private string _SelectedChoiceList;
public string SelectedChoiceList
{
get
{
return _SelectedChoiceList;
}
set
{
if (value != _SelectedChoiceList)
{
_SelectedChoiceList = value;
RaisePropertyChanged(nameof(SelectedChoiceList));
}
}
}
Etablissements = new ObservableCollection<Etablissement>((await _dataService.GetEtablissements().ConfigureAwait(false)));
Services = await _dataService.GetServicesAsync(false).ConfigureAwait(false);
Sections = await _dataService.GetSectionsAsync(_dataService.ParamGlobaux.IDEtablissement).ConfigureAwait(false);
Etablissement contain ID, Name, Years.
Service contain Color, ID, Name.
Section contain Color, ID, SectionName.
Edit 2 : I would like something like this exemple :
<ComboBox Name="CbService" HorizontalAlignment="Left" Margin="115,67,0,0" VerticalAlignment="Top" Width="150" ItemsSource="{Binding}" SelectionChanged="CbRecherche_SelectionChanged" KeyboardNavigation.TabIndex="1" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Libelle}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="gd" TextElement.Foreground="Black" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="Auto" MinWidth="50" />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Fill="{Binding Fond}" Grid.Column="0"/>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding ID}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding Libelle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
Currently my combobox displays a string. I want something like this :
In this example, there is an ID, a color only in the ID part, and a Name. I can't do that with my string at the moment.
I belive you may be able to reduce the size of your codes by removing the RaisePropertyChanged event as ObservableCollections already contain the INotifyPropertyChanged interface, I have made a simple example of how to use Datatemplate to display information from ObservableCollections.
Step 1: C# codes:
namespace WpfApp1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ComboBox1.ItemsSource = Services;
Services.Add(new ServiceSectionModel { Color = Brushes.Red, ID = "Clem", Name = "Clementine" });
Services.Add(new ServiceSectionModel { Color = Brushes.White, ID = "011", Name = "Logistique" });
Services.Add(new ServiceSectionModel { Color = Brushes.Green, ID = "MBT", Name = "Montbrilland" });
}
public class ServiceSectionModel
{
public string ID { get; set; }
public string Name { get; set; }
public SolidColorBrush Color { get; set; }
}
ObservableCollection<ServiceSectionModel> Services = new ObservableCollection<ServiceSectionModel>();
}
}
Step 2: XAML codes:
<ComboBox x:Name="ComboBox1" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Background="{Binding Color}" Text="{Binding ID}" Margin="0,0,10,0" Padding="5,2,10,2"></TextBlock>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Items added to the ListBox at runtime never gets focused

As you can see in the image above, I have three columns in listbox.
First Column is FirstName, Second Column is LastName and Third Column is Age.
When I hit Enter on the Age Column a new Person is added to the list. Which eventually reflects the changes in ListBox.
Problem:
When I Press Enter on Age Column I get a new Person added as expected. But the focus does not go to the next ListItem. No matter how many times I press Enter I never get focus to the items added Programmatically.
Sample:
I have created a sample project that reproduces the issue:
Download Sample Project
I have a ListBox as follows:
<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}">
<ListBox.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="CurrentItemGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
<TextBox Grid.Column="1" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" Margin="5,0"/>
<TextBox Grid.Column="2" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Margin="5,0" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
IEventAggregator eventAggregator;
public MainWindowViewModel(IEventAggregator _eventAggregator)
{
People = new ObservableCollection<Person>();
People.Add(new Person());
eventAggregator = _eventAggregator;
DeleteUnwantedOrderItemTransactionCommand = new RelayCommand(DeleteUnwantedOrderItemTransaction);
AddNewOrderItemTransactionCommand = new RelayCommand(AddNewOrderItemTransaction);
}
public RelayCommand DeleteUnwantedOrderItemTransactionCommand { get; set; }
public RelayCommand AddNewOrderItemTransactionCommand { get; set; }
private ObservableCollection<Person> _People;
public ObservableCollection<Person> People
{
get
{
return _People;
}
set
{
if (_People != value)
{
_People = value;
OnPropertyChanged("People");
}
}
}
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (_SelectedPerson != value)
{
_SelectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
}
protected void DeleteUnwantedOrderItemTransaction(object obj)
{
if (SelectedPerson.FirstName == "")
{
People.Remove(SelectedPerson);
}
if (People.Count == 0)
{
People.Add(new Person());
}
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
protected void AddNewOrderItemTransaction(object obj)
{
if (SelectedPerson == People.Last())
People.Add(new Person());
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In Code-Behind:
public partial class MainWindow : Window
{
IEventAggregator _eventAggregator = new EventAggregator();
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel(_eventAggregator);
_eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Subscribe(MoveToNextUIElement);
}
void MoveToNextUIElement(bool obj)
{
// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus != null)
{
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
The moment you fire your event you have added your element to your VM, but it might not have been bound and created by the View (ListBox) yet. Dispatching the focus request with a low priority might help. Also check if you can access the element by pressing TAB, so you know the traversal works.
elementWithFocus.Dispatcher.Invoke(() =>
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)),
DispatcherPriority.Input); // !
Virtualization of the ListBox Elements could also be an issue. You have to bring the added item into view.
cheeers.

MouseBinding Gesture in wpf not binding to DataContext wpf

I do not understand why my command is not properly executing when the user double clicks the listview item. I know for a fact the error occurs in the way it's binding. I'm not sure how to properly bind to the windows data context.
How can I bind to the windows data context from a nested control.
Here is the problematic code isolated...
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EditCustomerCommand}"/>
</Grid.InputBindings>
MainWindow.xaml
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<!--<Grid>
<ContentPresenter Content="{Binding ActiveViewModel}"></ContentPresenter>
</Grid>-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Add New Person" Command="{Binding AddNewPersonCommand}"/>
<ListView Grid.Row="1" ItemsSource="{Binding People}">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Margin="0">
<StackPanel Orientation="Horizontal">
<Button Content="X" Width="20"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.RemovePersonCommand}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
/>
<Label Content="{Binding FirstName}"/>
</StackPanel>
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EditCustomerCommand}"/>
</Grid.InputBindings>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.Resources>
<ListView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteCustomersCommand}"/>
</ListView.InputBindings>
</ListView>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Label Content="# People"/>
<Label Content="{Binding PersonCount}"/>
</StackPanel>
</Grid>
</Window>
Please try the next solution, here I've used the freezable proxy class to get the access to the main view model. This case is similar to your problem, since the column can't access its parent data context directly.
Xaml code
<Window x:Class="DataGridSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataGridSoHelpAttempt="clr-namespace:DataGridSoHelpAttempt"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<dataGridSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid x:Name="MyGrid">
<Grid.Resources>
<dataGridSoHelpAttempt:FreezableProxyClass x:Key="ProxyElement" ProxiedDataContext="{Binding Source={x:Reference This}, Path=DataContext}"/>
</Grid.Resources>
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding DataSource}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Visibility="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Header="Comments" Binding="{Binding Comments}" Width="150"/>
<DataGridTextColumn Header="Price (click to see total)" Binding="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Button Content="Show Description" Command="{Binding Command}"></Button>
</StackPanel>
</Grid>
View model and models
public class MainViewModel:BaseObservableObject
{
private Visibility _visibility;
private ICommand _command;
private Visibility _totalsVisibility;
private double _totalValue;
public MainViewModel()
{
Visibility = Visibility.Collapsed;
TotalsVisibility = Visibility.Collapsed;
DataSource = new ObservableCollection<BaseData>(new List<BaseData>
{
new BaseData {Name = "Uncle Vania", Description = "A.Chekhov, play", Comments = "worth reading", Price = 25},
new BaseData {Name = "Anna Karenine", Description = "L.Tolstoy, roman", Comments = "worth reading", Price = 35},
new BaseData {Name = "The Master and Margarita", Description = "M.Bulgakov, novel", Comments = "worth reading", Price = 56},
});
}
public ICommand Command
{
get
{
return _command ?? (_command = new RelayCommand(VisibilityChangingCommand));
}
}
private void VisibilityChangingCommand()
{
Visibility = Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
public ObservableCollection<BaseData> DataSource { get; set; }
public Visibility Visibility
{
get { return _visibility; }
set
{
_visibility = value;
OnPropertyChanged();
}
}
public ObservableCollection<BaseData> ColumnCollection
{
get { return DataSource; }
}
public Visibility TotalsVisibility
{
get { return _totalsVisibility; }
set
{
_totalsVisibility = value;
OnPropertyChanged();
}
}
public double TotalValue
{
get { return ColumnCollection.Sum(x => x.Price); }
}
}
public class BaseData:BaseObservableObject
{
private string _name;
private string _description;
private string _comments;
private int _price;
public virtual string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public virtual object Description
{
get { return _description; }
set
{
_description = (string) value;
OnPropertyChanged();
}
}
public string Comments
{
get { return _comments; }
set
{
_comments = value;
OnPropertyChanged();
}
}
public int Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged();
}
}
}
Freezable proxy code
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof (object), typeof (FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object) GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
All is done due to the freezable object code, please take the solution as the starting point to your research. I'll glad to help if you will have problems with the code.
Regards.
I'll glad to help you

Prevent showing details when hyperlink is clicked

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; }
}
}

Categories