Bug with DataBinding in WPF Host in Winforms? - c#

I've spent far too much time with this and can't find the mistake. Maybe I'm missing something very obvious or I may have just found a bug in the WPF Element Host for Winforms.
I am binding a ListView to a ObeservableList that lives on my ProductListViewModel.
I'm trying to implement searching for the ListView with the general Idea to just change the ObservableList with a new list that is filtered.
Anyway, the ListView Binding code looks like this:
<ListView ItemsSource="{Binding Path=Products}" SelectedItem="{Binding Path=SelectedItem}" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the ViewModel code is as vanilla as it can get:
private ObservableCollection<ProductViewModel> products;
public ObservableCollection<ProductViewModel> Products
{
get { return products; }
private set
{
if (products != value)
{
products = value;
OnPropertyChanged("Products");
}
}
}
Now the problem here: Once I debug into my OnPropertyChanged method, I can see that there are no subscribers to the PropertyChanged event (it's null), so nothing happens on the UI..
I already tried Mode=TwoWay and other Binding modes, it seems I can't get the ListView to subscribe to the ItemsSource...
Can anyone help me with this? I'm just about to forget about the ElemenHost and just do it in Winforms
greetings Daniel

Is there any binding error in the output window?
By the way, you should consider getting the collection view wrapping your products, and then filtering the view, instead of replacing the whole collection.
The code would be something like:
var collectionView = CollectionViewSource.GetDefaultView(Products);
collectionView.Filter += item => ...;

Related

Update SortMemberPath on property change

I have a property, SortingName, used as the SortMemberPath for one of my columns in my DataGrid. However, I would like this property to get updated every time the header is clicked (i.e. call its setter again).
Here is the property:
[XmlIgnore]
public virtual string SortingName {
get { return m_sortingName; }
set
{
if (m_sortingName == null)
m_sortingName = value;
m_sortingName = m_sortingName.StartsWith("_") ? value : "_" + value;
}
}
And here is the XAML for the DataGrid it's used in (the first column is the :
<DataGrid.Columns>
<DataGridTemplateColumn Header="Templates"
Width="200" SortMemberPath='SortingName'>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type request:ModelDocument}">
<TextBlock TextAlignment="Left">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding PrettyName}"/>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...remaining columns
I've tried to use a Binding for the SortMemberPath, and assigning an UpdateSourceTrigger=PropertyChanged, but it did not seem to do anything. I had also attempted this method, but had issues with the how my ItemsSource is defined. So I would prefer to update the property on click instead.
To solve this I simply created a function in my custom class that extends off DataGrid, and added it to its Sorting event handler.
public DataGridEx()
{
Sorting += UpdateSorting;
}
The UpdateSorting function appends an ASCII character at the beginning of the string depending on what the order I'd like the item to stay in. In this example I want folder type items to stay at the top, so when the list is ascending I add a low alphabetical character (e.g. !), and vice versa for the descending list. This way the folders are always at the top.
This isn't the best solution in my opinion (and honestly not very related to the question itself) but it works for now. If anyone has a better answer please feel free to post!

WPF MVVM Light Multiple ListBoxItems bound to same object

I have UserControl containing a procedurally generated ItemsControl. Each item in the ItemsControl contains a ListBox and there is no consistent number of how many items will be generated. The selected item in the listbox is bound to am object (SelectedClass) in the ViewModel. The initial value of the SelectedClass object is null.
The scenario I am running into is this:
User selects ListBoxItemA from ItemsControlItemA, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemB, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemA, but since the selection in that list is still considered to be the same item from step 1, PropertyChanged does not fire, and the SelectedClass object remainsListBoxItemA from ItemsControlItemB.
So my question is, how do i get the UpdateSourceTrigger event to fire OnClick rather than on PropertyChanged, and is that even the best way to approach it? I'm using the MVVM Light framework.
Thanks
<ItemsControl ItemsSource="{Binding AllUpcomingClasses}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding classDescription}" />
<ListBox Name="availableClasses"
ItemsSource="{Binding ClassInstances}"
SelectedItem="{Binding
DataContext.SelectedClass,
Mode=TwoWay}
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type UserControl}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<TextBlock Text="{Binding ClassDate}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Cleaned up the example a bit for readability.
You could handle the PreviewMouseLeftButtonDown event of the ListBoxItem container and "manually" set the SelectedItem property of your view model if the clicked item is the one that is already selected:
<ListBox SelectedItem="{Binding SelectedItem}" xmlns:s="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
<s:String>A</s:String>
<s:String>B</s:String>
<s:String>C</s:String>
</ListBox>
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
if (lbi != null)
{
YourViewModel vm = DataContext as YourViewModel;
if (vm != null)
{
var selectedItem = lbi.DataContext as YourObjectType;
if (vm.SelectedItem == selectedItem)
{
vm.SelectedItem = selectedItem;
e.Handled = false;
}
}
}
}
If you don't want to handle this in the code-behind of the view you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf. The former approach doesn't really break the MVVM pattern though since you are just kind of "extending" the ListBox control functionality to be able to set the same view model source property that the ListBox control sets for you when you select a new item. This functionality belongs to the view or the control.

Databinding the ListBox SelectedItems attribute

I know that the ListBox has both a SelectedItem and SelectedItems attribute and that only the SelectedItem attribute can be used with databinding. However, I've read in multiple locations that by setting up a setter like so
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
and then by adding the following property
public IEnumerable<Item> SelectedItems
{
get
{
return Items.Where(x => x.IsSelected);
}
}
I can use the SelectedItems property to get the all items that were selected. I mainly have two questions regarding this:
Where does the Item and Items in the property come from? I've not been able to find a using directive that will remove the error preventing me from using it.
Is this the recommended way to do what I'm trying to achieve. If not, then what would you recommend?
Oh, before I forget I thought I should mention that I'm using both MVVM Light and Fody's PropertyChanged.
I'm not quite sure about the articles and their exact solution for doing so but my speculations is this:
The whole page has a ViewModel named MyPageViewModel.
MyPageViewModel has an ObservableCollection named Items.
Item is ViewModel type (derived from DependencyObject)
Item has a DependencyProperty named IsSelected
Given these assumptions you can see where can all things fit.
If the DataContext of the whole page is MyPageViewModel, then in this xaml code first IsSelected refers to a property of ListBoxItem, and the second one refers to a property in ViewModel of Item.
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
//This is inside MyPageViewModel
//Item is a ViewModel type
public IEnumerable<Item> SelectedItems
{
get
{
//Items is ObservableCollection<Item>
return Items.Where(x => x.IsSelected);
}
}
//This is also inside MyPageViewModel
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items { get { return _items; } }
The whole scenario can go the other way too. I mean it can be implemented in View instead of ViewModel. Maybe derive from ListBox and override a few things including SelectedItems. But adding these sorts of complexities are better done to ViewModel than View.

ObservableCollection + ResourceDictionnary + Listbox PropertyChanged does not work

I realize my own Calendar.
I did a Generic.xaml (ResourceDictionnary) which contains my new control. I have a Calendar.class who implement :Control.
In my Calendar class I have a ObservableCollection<Day> _days. I put DataContext = this;
A Day contains my ObservableCollection<MyObject> ListeTache.
And Day.Class Implement INotifyPropertyChanged and have my event :
public event PropertyChangedEventHandler PropertyChanged;
But when I update my Listbox, I have to reload my calendar manually to see any changes.
Am I missing something ?
Thank you for the help.
My ObservableCollection<MyObject> :
public ObservableCollection<Tache> ListeTache
{
get { return this._listeTache; }
set
{
_listeTache = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ListeTache"));
}
}
}
My Generic.xaml look like this :
<Grid x:Name="LayoutTache">
<ListBox x:Name="ListeTaches" ItemsSource="{Binding ListeTache,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" FontSize="10" PreviewMouseDown="PreviewMouseDownClick_clear" MouseDoubleClick="doubleClic" IsSynchronizedWithCurrentItem="False">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding AffichageCalendrier}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Resources>
<ContextMenu x:Key="MonMenu">
<MenuItem Header="Supprimer" Click="MonMenuDel_Click" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="AntiqueWhite"></Setter>
<Setter Property="ContextMenu" Value="{StaticResource MonMenu}"/>
</Trigger>
</Style.Triggers>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightGreen" />
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
After somme reply :
How I can do that ? I have to add something like this in my Day.cs class :
_listeTache.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_listeTache_CollectionChanged);
void _listeTache_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new NotImplementedException();
}
I never catch the event...
Thanks for all
When designing a custom control, it is customary not to set the DataContext to this... in fact, don't set it to anything as this enables it to be set from outside the control. Instead, you should reference your property from generic.xaml using a RelativeSource Binding:
<ListBox x:Name="ListeTaches" ItemsSource="{Binding ListeTache, RelativeSource={
RelativeSource AncestorType={x:Type YourXamlNamespacePrefix:Calendar}}}" ... />
It should also be noted that using UpdateSourceTrigger=PropertyChanged, Mode=TwoWay on an ItemsSource Binding is pointless as the ItemsControl cannot update the source collection.
If you still can't access the property, then you must either ensure that you correctly implement the INotifyPropertyChanged interface in Calendar.cs, or you can implement your ListeTaches property as a DependencyProperty instead.
UPDATE >>>
You've clearly done something wrong... it's really not that complicated. Follow the links that I provided to declare a DependencyProperty in your Calendar.cs class. Do not set the DataContext. Use the RelativeSource Binding that I showed you, correctly setting up the proper XAML Namespace... that's it!
Just one last thing... you did add a WPF Custom Control Library project into your application, didn't you? You need to have something like this in your Calendar class' static constructor:
static Calendar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Calendar),
new FrameworkPropertyMetadata(typeof(Calendar)));
}
Perhaps it would help if you read through the Control Authoring Overview page on MSDN to ensure that you are doing it correctly?
UPDATE 2 >>>
Ok, so after noticing something in your comments, I think that I know what your problem is. #XAMlMAX asked you
have you tried removing that null check for your PropertyChanged in Listtache?
You replied
When I remove it, I catch TargetInvocationException.
I think that that's your problem... that means that your PropertyChanged event is null... that means that you have not attached a handler to it yet... it's not being used. Try attaching a handler to it and your ListeTache collection should display fine.

hide or remove border of wpf button in C# code

I have a stackpanel named "mystack" in my xaml file and I am adding buttons in it dynamically from the .cs file and want to remove the border of buttons in C# .cs file
what I really want is to populate this stackpanel with the buttons coming from a list of string
thanks in advance
xaml:
<Grid HorizontalAlignment="Left" Height="227" Margin="10,10,0,0" Grid.Row="2"
VerticalAlignment="Top" Width="530">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="mystack" HorizontalAlignment="Left" Grid.Row="2"
VerticalAlignment="Top" Width="520"/>
</ScrollViewer>
</Grid>
.cs:
public List<String> Schools()
{
List<String> l = new List<string>();
l.Add("SST");
l.Add("SBE");
l.Add("SSH");
return l;
}
I agree with HighCore, you generally do not want to manipulate the UI elements in your code.
To remove the Border of the buttons you can set a Button's BorderThickness property to "0" in Xaml or to new Thickness(0) in the code-behind.
i.e.
myButton.BorderThickness = new Thickness(0);
EDIT:
Okay, I noticed your updated question. I would create a property that stores your list of schools and bind to it in a way similar to this:
public List<string> Schools
{
get { return _schools; }
set { _schools = value; }
}
Somewhere you need to set the DataContext of the control to your class containing the Schools property. If you are dynamically updating the list of Schools you'll need to implement INotifyPropertyChanged so the UI knows when to update. And then your Xaml would look something like this:
<ItemsControl ItemsSource="{Binding Schools}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl>
You can't remove button's border like: btn.BorderThicknes=new Thickness(0);
See this: Removing button's border
The fast Fix:
What I had to do to effectively hide the button border - and due to the button control template I believe which utilizes and changes Button border (i.e. even if you remove it it'd draw it on some trigger I believe)...
...was to set BorderBrush="Transparent" as well (I always do BorderThickness as well but I'm guessing it's not needed - only for visual/layout look'n'feel)
i.e. setting thickness alone is not enough.
I'm really not sure that's the bets way to do it, or actually I'm
quite sure there must be something smarter - but I always end up with
that.
The Right Way:
Proper way - and recommended - is to write your own Button template -
based on the Microsoft official one - or base it on it - and do what
you need w/o borders.
For the code behind/C#:
You really don't need that as per your changed question - do what others suggested already
the best way to do this is :
<Style TargetType="Button">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0"/>
</Style>
</Style.Resources>
</Style>
what I really want is to populate this stackpanel with the buttons
coming from a list of string
That's called a ListBox:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0"/>
<!-- Whatever other customizations to the button -->
</DataTemplate
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> Items {get;set;}
public ViewModel()
{
Items = new ObservablecCollection<string>();
Items.Add("String1");
Items.Add("String2");
Items.Add("String3");
}
}
You need to learn the MVVM pattern.

Categories

Resources