Binding Property to Two Different Controls [duplicate] - wpf

This question already has answers here:
Binding usercontrol property to custom class
(1 answer)
Creating generalized user controls with MVVM Light
(1 answer)
Datacontext conflicts
(1 answer)
Closed 4 years ago.
I have a WPF form with a content control and a custom control. The content control swaps in views based on a radio button selection. Once the user takes an action on the view, I set the nocustomer on the parent viewmodel (the WPF form containing the two controls) to false. When this occurs, the visibility of content control correctly disappears. Unfortunately, the visibility of the custom control remains unchanged (it should have also disappeared). I'm actually perplexed because in my mind they have the exact same implementation and therefore should behave the same.
<ContentControl x:Name="ViewSwap" Content="{Binding SearchingViewModel}"
Visibility="{Binding NoCustomer, Converter={StaticResource
BooleanToVisibilityConverter}, Mode=OneWay}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SearchOptions, Path=IsSelected}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource AddressTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<views:CTACallSubmit x:Name="CallSubmit"
Visibility="{Binding NoCustomer, Converter={StaticResource
BooleanToVisibilityConverter}, Mode=OneWay}"/>
Update:
MainWindow's DataContext
public partial class CTALight : Window
{
public CTALight()
{
InitializeComponent();
this.DataContext = CTALightViewModel.GetInstance();
}
}
MainViewModel
public class CTALightViewModel : ObservableObject
{
public static CTALightViewModel _mainViewModel;
public static CTALightViewModel GetInstance()
{
if (_mainViewModel == null)
_mainViewModel = new CTALightViewModel();
return _mainViewModel;
}
private CTALightViewModel()
{
}
}
CTACallSubmit DataContext
<UserControl.DataContext>
<viewmodel:CTACallSubmitViewModel />
</UserControl.DataContext>

The following creates a new instance of CTACallSubmitViewModel and sets the DataContext of the UserControl to this one.
<UserControl.DataContext>
<viewmodel:CTACallSubmitViewModel />
</UserControl.DataContext>
This means that the binding to the NoCustomer property of the other view model won't work unless you specify a source of the binding:
<views:CTACallSubmit x:Name="CallSubmit"
Visibility="{Binding DataContext.NoCustomer,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"/>
Setting the DataContext of a UserControl like this is usually a bad idea as it breaks the inheritance of the parent's DataContext.

Related

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

How to bind DataTable to DataGridComboBoxColumn

I have and DataGridComboBoxColumn in the DataGrid a WPF window. I am assigning DataContext to Window as below:
cls = new MyClass
{
selValue = 2,
DataGrid = dtGrid,
ComboGrid = dtCombo
};
this.DataContext = cls;
Following is the XAML for DataGridComboBoxColumn:
<DataGridComboBoxColumn Header="Item Name" SelectedValueBinding="{Binding Path=Item_Id}" SelectedValuePath="ItemId" DisplayMemberPath="ItemName">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<!-- modified this code as per suggestion ///-->
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ComboGrid }" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ComboGrid }" />
</Style>
</DataGridComboBoxColu
mn.EditingElementStyle>
Still Combobox in grid showing blank. No data is being listed in Combobox. Then, I wrote following code in Windows codebehind, it start working.
((DataGridComboBoxColumn)this.testGrid.Columns[1]).ItemsSource = cls.ComboGrid.DefaultView;
Is there anyway to handle this case in XMAL itself using MVVM? I am reluctant to use this apporache.
If the itemsSource is not within the datagrids itemssource you will have to find ancestor:
<DataGridComboBoxColumn itemsSource="{binding RelativeSource={RelativeSource ancestortype=Page}, path=DataContext.YourComboboxItemsSource}" />
Assuming your datagrid is on a page, you can change the ancestortype to anything. You can use relativeSource on anything though. The reason for having to use this is that the itemssource you are trying to set is not part of the hierarchy so it can't find it. Hope this helps.
MVVM I would do something like:
public list<string> ComboboxGridItemsSource { get; set; }
//Then add some data in the property above.
ComboboxGridItemsSource.add("Hello world1"); , ect...
And when this list gets altered/updated remember to raise the property using INotifyPropertyChanged.
//After you implement INotifyPropertyChanged you can raise like this:
RaiseProperty("ComboboxGridItemsSource");
Using MVVM you generally wouldn't manually set properties directly to the control but rather bind properties to that control in xaml.

DataTrigger, Binding to nested properties via TemplatedParent

According to msdn, it should be perfectly legal, and possible, to bind something to a nested property:
<Binding Path="propertyName.propertyName2" .../>
<Binding Path="propertyName.propertyName2.propertyName3" .../>
In my case, it's not so, though...
I have a custom control, MyControl, with a dependency property ViewModel:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(IViewModel), typeof(MyControl));
public IViewModel ViewModel
{
get { return (IViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
and in the control template, I try to bind to properties in that viewmodel:
<Style TargetType="{x:Type my:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyControl}">
<Grid>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
<Button x:Name="MyButton" Content="Visible by trigger" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.ButtonVisible}" Value="True">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
.../>
In the viewmodel itself, I have a preoperty Text as follow:
public string Text
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("Text");
}
}
public bool ButtonVisible
{
get { return m_buttonVisible; }
set
{
m_buttonVisible = value;
OnPropertyChanged("ButtonVisible"); }
}
I get no bind errors, but things doesn't happend...
Any clues?
Edit
It looks like the bindings work half way. When the text is changed in the editbox, my Text property is set, but if the Text-property is set in code, the ui won't update.
Edit 2
Looks like my first attempt at simplifying the case before posting was a little to successful... As #Erno points out, the code that I posted seems to work OK.
I have looked at the original code some more, and added a trigger to the scenario. The original code uses triggers to show parts of the ui at given conditions. These are also binded to nested properties. I now think that these triggers fail to trigger. I have updated the code. If it still doesn't show whats wrong, I can post a sample application some where.
There is a comma missing:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
EDIT
Add Mode=TwoWay to the binding:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text, Mode=TwoWay}"/>
EDIT2
Got it! I could reproduce and fix it.
Replace the TemplatedParent with Self in the binding.
Read this explanation

Hide ListViewItem in WPF ListView

How can I hide a ListViewItem in a bound ListView? Note: I do not want to remove it.
Yeah, this is easy.
The first thing you need to do is to add a property to the class you are binding to. For example, if you are binding to a User class with FirstName and LastName, just add a Boolean IsSupposedToShow property (you can use any property you like, of course). Like this:
class User: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
public string LastName { get; set; }
private bool m_IsSupposedToShow;
public bool IsSupposedToShow
{
get { return m_IsSupposedToShow; }
set
{
if (m_IsSupposedToShow == value)
return;
m_IsSupposedToShow = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSupposedToShow"));
}
}
}
Then, remember, to hide some item, don't do it in the UI - no no no! Do it in the data. I mean, look for the User record that you want to hide and change that property in it behind the scenes (like in a View Model) - let the UI react. Make the XAML obey the data.
Like this:
<DataTemplate DataType="{x:Type YourType}">
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSupposedToShow}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<!-- your UI here -->
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="LastName" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
When you change IsSupposedToShow to false, then the XAML understands it is supposed to change the visibility of the whole DataTemplate. It's all wired up for you by WPF and presto, it's what you wanted in your question!
Best of luck!
The approaches that I'd follow, from most to least preferable:
In ListView.ItemContainerStyle, use a DataTrigger to set Visibility based on a bound property.
Use a style in the ItemTemplate, or in the DataTemplate for the items if you're getting default templates from the resource dictionary.
Set the ItemsSource for the ListView to a CollectionView, and handle the CollectionView's Filter event in code-behind. See MSDN's discussion of collection views for details.
Maintain a separate ObservableCollection as the ItemsSource for the ListView and add/remove items as appropriate.
Under no circumstances would I use a ValueConverter, because I have a possibly-irrational distaste for them.
I think that using a CollectionView is probably the most correct way of doing this, but they're kind of inelegant because you have to write an event handler to implement filtering.
Use a style with a trigger to set the items visibility to collapsed.
This page gave me the answer I needed: http://www.abhisheksur.com/2010/08/woring-with-icollectionviewsource-in.html (See section "Filtering".)
Wow, so much easier than XAML.
Example:
bool myFilter(object obj)
{
// Param 'obj' comes from your ObservableCollection<T>.
MyClass c = obj as MyClass;
return c.MyFilterTest();
}
// apply it
myListView.Items.Filter = myFilter;
// clear it
myListView.Items.Filter = null;
The approach with ListView.ItemContainerStyle
<ListView ItemsSource="{Binding Path=Messages}" Grid.Column="1" Grid.Row="1" x:Name="Messages"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False" >
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" >
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} => {1}">
<Binding Path="AuthorName" />
<Binding Path="ReceiverName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Jerry Nixon's answer did not work for me completely. I've got to change the xaml a little bit.
Collapsed list view item was using small layout space when I was using DataTemplate.Resources,
<ItemsControl>
<ItemTemplate>
<DataTemplate>
<Image Visibility='{Binding Converter=my:MaybeHideThisElementConverter}' />
</Image>
</DataTemplate>
</ItemTemplate>
</ItemsControl>
What we're doing here is delegating the decision to your implementation of MaybeHideThisElementConverter. This is where you might return Collapsed if the User property of your object is null, or if the Count is an even number, or whatever custom logic your application requires. The converter will be passed each item in your collection, one by one, and you can return either Visibility.Collapsed or Visibility.Visible on a case by case basis.

Change databinding on WPF ComboBox when Checkbox value changes

I have a WPF ComboBox which is databound to a Collection, but depending on wether a Checkbox is checked or not I'd like to vary which Collection the ComboBox is bound to.
The basic issue is I have a large collection of MyCustomer and I also have a filtered collection of MyCustomer - the filtering is quite intensive and I don't want to do it with a CollectionView for the main reason that it is already done, the filtered collection already exists - hence the need to simply switch the databinding of the combo.
I'm hoping for a pure XAML solution, obviously writing some code behind would be a relatively simple solutions but it doesn't feel like it should be required.
Here's an example using a DataTrigger to switch the collections:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel.Resources>
<x:Array x:Key="notes" Type="{x:Type sys:String}">
<sys:String>do</sys:String>
<sys:String>re</sys:String>
<sys:String>mi</sys:String>
</x:Array>
<x:Array x:Key="letters" Type="{x:Type sys:Char}">
<sys:Char>a</sys:Char>
<sys:Char>b</sys:Char>
<sys:Char>c</sys:Char>
</x:Array>
</StackPanel.Resources>
<CheckBox x:Name="chkLetters" Content="Use Letters"/>
<ListBox>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemsSource" Value="{StaticResource notes}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=chkLetters}" Value="True">
<Setter Property="ItemsSource" Value="{StaticResource letters}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
</StackPanel>
For you these wouldn't be different arrays, but probably different CollectionViewSources with filters or something, but the principle is the same.
Best way I know is to use some shell collection that internally 'gets' the right collection.
So you have your UnfilteredCollection and your FilteredCollection and then a property called BindingCollection which, in its 'getter,' evaluates some state (the checkbox would be bound to this state) to determine which collection to retrieve.
If you use MVVM for the databinding between the UI and the collections, one way to do it would be like this:
<!-- Your ComboBox binds to some shell collection -->
<ComboBox ItemsSource="{Binding BindingCollection}" />
<!-- The input to this item will determine which collection is internally exposed -->
<CheckBox IsChecked="{Binding UseFilteredSet}" />
And then have your ViewModel (middle-layer) file do something like this (I'm not including the implementation details of INotifyPropertyChanged, but I can if you wish):
private ObservableCollection<MyCustomer> UnfilteredCollection
{
get { return _unfilteredCollection; }
}
private ObservableCollection<MyCustomer> FilteredCollection
{
get { return _filteredCollection; }
}
// The public collection to which your ComboBox is bound
public ObservableCollection<MyCustomer> BindingCollection
{
get
{
return UseFilteredSet ?
FilteredCollection :
UnfilteredCollection;
}
}
// CheckBox is bound to this state value, which tells the bindings on the shell
// collection to refresh when the value of this state changes.
public bool UseFilteredSet
{
get { return _useFilteredSet; }
set
{
_useFilteredSet = value;
OnPropertyChanged("UseFilteredSet");
OnPropertyChanged("BindingCollection");
}
}

Resources