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
Related
I want to highlight(make bold and change its color) all the items whose text starts with the text of the combobox's textbox.
I have tried to google the above question but I am unlucky to get any similar results which would solve my problem.
I think just a hint might be more than enough to solve this problem. Though I am a newbie. If it is possible give me a simple example.
Update :
Here is the code that I tried:
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
TextSearch.TextPath="GroupName" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="ComboBox_PART_Editable">
<Setter Property="Foreground" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</VirtualizingStackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But I dont know with what should I replace ComboBox_PART_Editable and I don't want the whole text I just want to use Text.StartsWith
I assume that the items in your ComboBox are just plain string values. You will have to change that and create a class to display each item. The reason for this is that you will need some bool 'flag' property that you can bind to a DataTrigger that will highlight your entries according to your requirement. So you could do this:
public class CustomComboBoxItem : INotifyPropertyChanged
{
public string Value { get; set; } // Implement INotifyPropertyChanged correctly...
public bool IsHighlighted { get; set; } // ... here, unlike this example
}
Then you'd need a collection property in your code behind or view model:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
Again, you must implement the INotifyPropertyChanged interface correctly here. Then you could bind it to the ComboBox.ItemsSource property like this:
<ComboBox ItemsSource="{Binding Items}" ... />
By now, this should look like a normal ComboBox with text entries, so we have to provide a DataTemplate to tell the entries to get highlighted when a condition is met... that's what the IsHighlighted property is for:
<DataTemplate DataType="{x:Type YourXmlNamespacePrefix:CustomComboBoxItem}">
<TextBlock Text="{Binding Value}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
The final piece of the puzzle is to set the IsHighlighted properties according to your requirements. For this, we'll need to bind to the ComboBox.Text property so that we know what that value is in the code. For this, add another property next to the collection property and update the item's IsHighlighted properties inside whenever it changes:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
public string InputValue
{
get { return inputValue; }
set
{
inputValue = value;
NotifyPropertyChanged("Items");
for (int i = 0; i < Items.Count; i++)
{
Items[i].IsHighlighted = Items[i].StartsWith(inputValue);
}
}
}
...
<ComboBox ItemsSource="{Binding Items}" Text="{Binding InputValue}" ... />
Well that was a bit more thorough than I had intended, but there you go. Let me know how you get on.
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.
I am unable to bind two radiobuttons to my xaml form and IsChecked property with same group names for which I also used nullabletoboolconverters. however, the radiobuttons ischecked property does not get changed in my code(it is not at all hitting the breakpoint, once we hit the first radiobutton after second one) and I am binding ischecked properties of two of them seperately as I need to set the visibility of some other panel on the form based on the radiobuttons property.
the following is my xaml code and later is my viewmodel.cs code for radiobutton properties:
<RadiobuttonSelectedConverter x:Key="CheckedSelection"/>// declaring my converter class in my resource dictionary.
<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="3" Margin="10,5,0,0">
<RadioButton GroupName="RadiosGroup" IsChecked="{Binding IsRadioButton1,Mode=TwoWay,
Converter={StaticResource CheckedSelection}, ConverterParameter=true}">
First</RadioButton>
<RadioButton GroupName="RadiosGroup"
Margin="40,0,0,0" IsChecked="{Binding IsRadioButton2,Mode=TwoWay,
Converter={StaticResource CheckedSelection}, ConverterParameter=true}">Second</RadioButton>
</StackPanel>
private bool _isRadioButton1;
public bool IsRadioButton1
{
get
{
return _isRadioButton1;
}
set
{
if _isRadioButton1!= value)
{
_isRadioButton1= value;
IsRadioButton2= false;
OnPropertyChanged("IsRadioButton1");
}
}
}
private bool _isRadioButton2;
public bool IsRadioButton2
{
get
{
return _isRadioButton2;
}
set
{
if (_isRadioButton2 != value)
{
_isRadioButton2 = value;
IsRadioButton1= false;
OnPropertyChanged("IsRadioButton2");
}
}
}
the following is my Converter code:
[ValueConversion(typeof(bool?), typeof(bool))]
public class RadiobuttonSelectedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool param = bool.Parse(parameter.ToString());
if (value == null)
{
return false;
}
else
{
return !((bool)value ^ param);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool param = bool.Parse(parameter.ToString());
return !((bool)value ^ param);
}
}
Someone please help me resolving my issue thanks in advance..
Personally I wouldn't code associated RadioButtons like this at all. Since the selection behavior you want is the same used for a ListBox, I find it easiest to simply use a ListBox that is styled to use RadioButtons for each item.
The code-behind typically will contain
ObservableCollection<string> Options
string SelectedOption
and I will use this style for the ListBox:
<Style x:Key="RadioButtonListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
The style is applied like this:
<ListBox ItemsSource="{Binding Options}"
SelectedValue="{Binding SelectedOption}"
Style="{StaticResource RadioButtonListBoxStyle}" />
You can also use something else instead of a String for the collection, such as a ChildViewModel, then set your related View based on the current item, which means you don't have to bother with the Visibility of the associated panel in your ViewModel either.
<DockPanel>
<ListBox ItemsSource="{Binding OptionViewModels}"
SelectedValue="{Binding SelectedViewModel}"
Style="{StaticResource RadioButtonListBoxStyle}"
DockPanel.Dock="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
But as for your actual problem, I can think of 3 reasons it might be behaving incorrectly.
The first is outlined in Jay's answer: you are setting IsChecked2 to false in your setter for IsChecked1, and IsChecked2 sets IsChecked1 to false in its setter, so the end result is both values are false.
The second is it might be a problem with your converter. You said it a comment it was working correctly without the converter, so I think that may be part of the problem.
And last of all, I believe changing grouped RadioButtons will trigger an IsOptionA.IsSelected = false for the old item and an IsOptionB.IsSelected = true for the newly selected item, and its possible those two are getting crossed somewhere.
A couple of issues here.
You don't need a converter. Make IsRadioButton1 and IsRadioButton2 properties of type bool?, and the TwoWay Binding will suffice, or just leave it as bool if tri-state is not applicable for you.
The logic in your setters appears to be incorrect. In both cases, you are setting the value of the other RadioButton to false, so if IsRadioButton1 is true and you then set IsRadioButton2 to true, the setter will call IsRadioButton = false, and then that setter will call IsRadioButton2 = false. They will both end up being false.
You probably want this to read if(value) IsRadioButton2 = false;
edit
Actually, as I recall, RadioButton is not meant to be bound to a bool property the way a CheckBox is. I think you bind all of the RadioButtons to one property, and use a Converter with a ConverterParameter to set the property. I'll find an example and post in a minute.
Okay, here is one solution, using a derived RadioButton class that behaves purely with bindings: http://blogs.msdn.com/b/mthalman/archive/2008/09/04/wpf-data-binding-with-radiobutton.aspx
Here is a related SO question & answers: MVVM: Binding radio buttons to a view model?
I'm experiencing a weird problem...
What I'm trying to do is quite standard, I guess: aloowing the user to switch between Grid
and Icon modes in my ListView.
All is going well, but... The Icon view, instead of showing the items in wrapping rows, shows them in a single column, with each item occupying the whole width of the view. And I can't put my finger on what exactly is wrong... :-(
(I haven't earned enough XP on this forum yet, and it won't allow me to post images; I'll give the links to the screenshots instead)
What I want: http://i.stack.imgur.com/jYhVx.png
What I have: http://i.stack.imgur.com/PeAae.png
Here's the IconView style definition (in Themes\Generic.xaml):
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:IconView}, ResourceId=IconViewStyle}"
TargetType="{x:Type ListView}"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="ItemContainerStyle" Value="{Binding (ListView.View).ItemContainerStyle, RelativeSource={RelativeSource Self}}"/>
<Setter Property="ItemTemplate" Value="{Binding (ListView.View).ItemTemplate, RelativeSource={RelativeSource Self}}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
It's used in the corresponding Control class:
public class IconView : ViewBase
{
public static readonly DependencyProperty ItemContainerStyleProperty =
ItemsControl.ItemContainerStyleProperty.AddOwner(typeof(IconView));
public Style ItemContainerStyle
{
get { return (Style)GetValue(ItemContainerStyleProperty); }
set { SetValue(ItemContainerStyleProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty =
ItemsControl.ItemTemplateProperty.AddOwner(typeof(IconView));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemWidthProperty =
WrapPanel.ItemWidthProperty.AddOwner(typeof(IconView));
public double ItemWidth
{
get { return (double)GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty =
WrapPanel.ItemHeightProperty.AddOwner(typeof(IconView));
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
protected override object DefaultStyleKey
{
get
{
return new ComponentResourceKey(GetType(), "IconViewStyle");
}
}
}
And here's how all this is being used in the View.xaml (I'll omit the DataTrigger that assigns {DynamicResource IconView} to ListView's View, for brevity) :
<DataTemplate x:Key="IconViewItemTemplate">
<StackPanel Height="170" Width="170">
<Grid Width="150" Height="150" HorizontalAlignment="Center">
<Image Source="{Binding DefaultPicture.Path}" Margin="6,6,6,9"/>
</Grid>
<TextBlock Text="{Binding ID}" FontSize="13" HorizontalAlignment="Center" Margin="0,0,0,1" />
</StackPanel>
</DataTemplate>
<localControls:IconView x:Key="IconView"
ItemTemplate="{StaticResource IconViewItemTemplate}"
ItemWidth="180"/>
I am going nuts... And, to add to my frustration, Snoop doesn't see my application :-(
Please help! ;-)
Many thanks,
Alex
Most of your bindings might just be broken: (ListView.View).ItemWidth
The above path is interpreted differently than the paths you use in StoryBoard.TargetProperty for example. If you use parenthesis in a binding it signals a binding to an attached property.
From MSDN, emphasis mine:
The path is specified in XAML that is in a style or template that does not have a specified Target Type. A qualified usage is generally not valid for cases other than this, because in non-style, non-template cases, the property exists on an instance, not a type.
So change those respectively, in the above example: View.ItemWidth
Well, I found the culprit.
Turns out that problem is not in the snippets I included in the question, but rather in something I left out - the ListView.GroupStyle definition on the ListView itself.
After removing it, the list is shown the way I expect it to be.
Thank you to everyone that considered my question!
Alex
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.