WPF: Clickable button in ItemTemplate of a ComboBoxItem with IsEnabled=false - c#

Trying to set up a WPF ComboBox;
some of its items should not be selectable, so I'm binding IsEnabled to some property of the underlying item.
At the same time, I need to define an ItemTemplate that contains e.g. a Button.
This button needs to be clickable, even if the item is not selectable (worth nothing a click on the button should not select the item as such of course; it will trigger a command performing some background actions, which will eventually make the underlying item selectable)
However, when ComboBoxItem.IsEnabled = false, it seems even the button automatically gets disabled.
Brief example:
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding CanSelectItem}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<!-- This button isn't clickable when ComboBoxItem.IsEnabled = false .. but it should be! -->
<Button Content="Click me" Command="{Binding SomeCmd}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Is there any way to circumvent this? E.g., set some items as non-selectable, however define a button in the ItemTemplate that remains clickable regardless?
Thanks

When you remove the ComboBox.ItemContainerStyle you can set the IsEnabled property of each element in the DataTemplate
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.IsEnabled}"/>
<Button Content="Click me" Command="{Binding SomeCmd}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
The combobox item will still be enabled but the TextBlock will be disabled.
The binding is just an example. Depends on where your IsEnabled property is. In my example the property is in the viewmodel which is DataContext of your Window.

For future reference, have found another way of doing this, based on this answer:
WPF override IsEnabled from Parent
So basically creating a class derived from button that overrides the default IsEnabled behavior.
Benefit is that this seems to do exactly what I was looking for, but it does change one of WPF's pretty.. default behaviors, so might need to be taken with a bit of care

Related

Hide checkbox, but show its content

Is it possible to hide a checkbox, but leave its content visible?
<ListBox
ItemsSource ="{Binding MyItemCollection}"
SelectionMode="Single"
Width="300"
Height="320">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}">
<CheckBox.Content>
<TextBlock Text="{Binding Item.Code}"/>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<CheckBox Content="Edit Mode"
IsChecked="{Binding Path=EditModeSelected, Mode=TwoWay}">
</CheckBox>
</StackPanel>
I would like to hide the checkboxes in the list box when I turn Edit Mode off (so it should be binded to EditModeSelected), but the text should be left visible.
In order to do so You can keep two TextBlocks. In edit mode visible CheckBox and hide TextBlock and in reader mode vice versa. I hope this may help. As DataTemplate can have only one child here's the fix
Create a Window Resource like below. Two Data Templates were created one for edit mode and another for Reader Mode.
<Window.Resources>
<DataTemplate x:Key="EditModeTemplate">
<CheckBox IsChecked="{Binding IsChecked}">
<CheckBox.Content>
<TextBlock Text="{Binding Item.Code}"/>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="ReaderModeTemplate">
<TextBlock Text="{Binding Item.Code}"/>
</DataTemplate>
</Window.Resources>
Now in .cs file assign the Date Template as per requirements.
if (EditMode)
{
DemoCollection.ItemTemplate = this.Resources["EditModeTemplate"] as DataTemplate;
}
else
{
DemoCollection.ItemTemplate = this.Resources["ReaderModeTemplate"] as DataTemplate;
}
3 possible solutions come in my mind - two of them more or less "hacks" and one more or less clean solution:
A checkbox and textblock for every item - you can get problems with margins etc
A checkbox with no content (which is only visible when in edit mode), and a textblock which is always visible
Take the default controltemplate for checkbox (Default ControlTemplate for CheckBox) and bind the visibility of the checkbox
Here is a Xaml only solution pulled from a project I am working on. In this case "ShowCheck" is a field in the current binding context saying whether or not to show the check.
<CheckBox Content="{Binding Name}">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowCheck}" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<ContentControl Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
Basically if the checkbox should be invisible, then I use a style and a trigger to change the checkbox's template to something without the checkbox. My implementation the content is just a string, so this works. If you were putting more complicated objects into the checkbox, you might need to shuttle the ContentTemplate, ContentTemplateSelector, and related fields into the ContentControl that is used to replace the checkbox

How to change Tab from TabControl in WPF without violating the MVVM pattern

My WPF Windows contains a TabControl which displays content on different tabs. A click on the button below executes a method via ICommand interface / Binding. The called method generates text which is intended to be displayed in the second tab.
How can I switch to the second tab on button click without violating the MVVM Pattern?
I tried to bind the TabItem.IsSelected Property to something in my ViewModel but I wanted to use the other tabs (tab1) as well.
Any thoughts?
I found it out by myself.
The key is a two way binding. When the button is clicked it sets the property DisplayXamlTab true. The IsSelected attribute is bound to this variable. if another tab is clicked the binding will set the DisplayXamlTab Property to false.
Note: UpdateSourceTrigger=PropertyChanged is also very important
Code comes below:
XAML:
<TabItem Header="XAML" IsSelected="{Binding DisplayXamlTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid Background="#FFE5E5E5">
<TextBox x:Name="TxtXamlOutput" IsReadOnly="True" Text="{Binding XamlText, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
</Grid>
</TabItem>
C# Property:
private bool displayXamlTab;
public bool DisplayXamlTab
{
get { return this.displayXamlTab; }
set
{
this.displayXamlTab = value;
this.RaisePropertyChanged("DisplayXamlTab");
}
}
if you're going the MVVM way you're going to create two dependency properties in the code behind:
ObservableCollection<ItemType> Items;
ItemType MySelectedItem;
Then, bind the TabControl ItemsSource property to the Items and bind the SelectedItem property to MySelectedItem
<TabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<... here goes the UI to display ItemType ... >
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
When you want to change the selected tab, simply update the MySelectedItem dependecy property
Although this question is fairly old and well answered already, I thought I'd add this additional answer to demonstrate an alternative way of changing the selected TabItem in a TabControl. If you have a view model for each TabItem, then it can be helpful to have an IsSelected property in it to determine whether it is selected or not. It is possible to data bind this IsSelected property with the TabItem.IsSelected property using the ItemContainerStyle property:
<TabControl ItemsSource="{Binding MenuItems}" TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" Margin="0,0,10,0" />
<TextBlock Text="{Binding HeaderText}" FontSize="16" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<ContentControl Content="{Binding ViewModel}" />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
You can now change the selected TabItem from the parent view model like this:
MenuItems[0].IsSelected = true;
Note that because this property is data bound to the TabItem.IsSelected property, calling this...:
MenuItems[1].IsSelected = true;
... will infact also automatically set the MenuItems[0].IsSelected property to false. so if the view model that you are working with has its IsSelected property set to true, then you can be sure that its related view is selected in the TabControl.
You can create a binding between the view model and the TabControl.SelectedIndex property - i.e., 0 selects the first TabItem , 1 selects the second, etc.
<TabControl DataContext="..." SelectedIndex="{Binding SomeVmProperty}" ...
(alternatively, depending on how you've got things set up, you could bind against SelectedItem...)
You'll likely want to use some sort of "Event Aggregator" pattern (I.e. the Messenger class in MVVM Light) to broadcast some sort of "navigation" message. Your View - the TabControl - can listen for the specific message, and navigate to Tab2 when the message is received.
Alternatively, you can bind the "SelectedItem" property of the TabControl to your ViewModel, and simply call CurrentTab = MySecondTabViewModel from within your VM. This is the approach recommended by #HighPoint in the comments to the OP, but I'm not a fan; see below. Another caveat to this approach is that you need to be familiar with DataTemplates, as you will need to map a view to each ViewModel which you display.
I personally like the first approach, because I don't consider it to be a "responsibility" of the ViewModel to handle tab navigation. If you simply alert your View when data changes in your ViewModel, you allow the View to decide whether or not it wants to change tabs.

Combobox selections

I have this combobox which contains a data tempate with a checkbox and a textbox. All is working but I would like to make it a little bit easier to select a value. Right now I have to click on the chechbox to change the value of the check box. Now I would like to be able to just click on the item in the combobox which also should toggle the checkbox.
Is this possible? If yes then how?
Here is a picture of my solution right now
Here is the code for my combobox
<ComboBox Name="employeeComboBox" Margin="2,0,2,0"
ScrollViewer.CanContentScroll="False"
DataContext="{Binding EmployeesOverviewViewModel, Source={StaticResource ViewModelLocator}}"
ItemsSource="{Binding Employees}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}" Margin="2,0,2,2" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Change your DataTemplate to this:
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}"
Margin="2,0,2,2"
Content="{Binding Path=Name}"
VerticalAlignment="Center"/>
</DataTemplate>
and it should work.
Why don't you use the Content property on the CheckBox?
<CheckBox Content="Hello, World" />
This way, the checkbox would toggle even when you click on the text (content).
As for your specific case, you can bind Name to Content instead of creating a separate TextBlock for it, and it should work as you want it to.
Whats happning here is, Your Checkbox and text are two different entities, you need to make them one, by simply using checkbox's text property and binding it. that way whenever you click on text your checkbox gets selected.

How to access a Checkbox inside Listbox?

I have a listbox and I have set the itemstemplate as shown below.
XAML:
<ListBox ItemsSource="{Binding DataList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CheckBox" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock x:Name="TextBlock" Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,0,0,10" FontSize="26.667" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to get which all check box has been selected.Is there any way to get checkbox control for each item so that I can check its IsChecked property.
I can think of a way of binding the IsChecked property.But Is there any other way to do it?
Yes. One way to do is to bind the IsChecked property. And if you are using MVVM, probably that's the right way to do it.
Anyways, if you don't want to go the binding way, and assuming you want to iterate over all items of listbox, and prepare a list of checked items, see if this helps:
WPF - Find a Control from DataTemplate in WPF
If you're already binding to the Title property in the item template then it would certainly make sense to bind to IsChecked, too.
If you really need to, you can walk the visual tree by using the VisualTreeHelper to find the CheckBox instances.
Binding the IsChecked property to a boolean property on your object instance contained within DataList would be the simplest and cleanest way. Alternatively, if you want to avoid code behind, then you could write an attached property.
See also How to access a specific item in a Listbox with DataTemplate?
I bet it cannot be simpler than that:
<ListBox SelectionMode="Multiple" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF Expander IsExpanded binding

I have an Expander control with its IsExpanded property bound to a bool in the mvvm model. The binding works fine until you dont touch the expander. Once you click the arrow in the expander to expand, the binding stops working. Setting the bool ShowPreview to false in the model doesn't collapse the expander.
<Expander Name="pExpander"
IsExpanded="{Binding Path=ShowPreview,Mode=OneWay}"
Header="Preview">
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"></TextBlock>
</Expander>
If you remove Mode=OneWay does that fix the problem?
Upon reading your other CTQ (changes to the GUI do not affect the model), I don't have a good suggestion for how to limit the change being seen by the underlying data. What is the difference in:
myModel.MyProperty = true; // in *your* code behind
And
myModel.MyProperty = true; // done by a binding
What caught me out here is that IsExpanded is OneWay by default, so
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Expanded}"/>
</Style>
doesn't work the way I expected. Only if you add Mode=TwoWay, then it works (i.e. the item starts paying attention to my Expanded property, and updating it), as in
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Expanded, Mode=TwoWay}"/>
</Style>
With Silverlight I do this:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
<Expander Name="pExpander" IsExpanded="True" Header="Preview">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding ShowPreview, Mode=OneWay}">
<ei:ChangePropertyAction PropertyName="IsExpanded" Value="{Binding ShowPreview, Mode=OneWay}"/>
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"></TextBlock>
</Expander>
<Expander Name="pExpander1" IsExpanded="True" Header="Preview 1">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding ShowPreview, Mode=OneWay}">
<ei:ChangePropertyAction PropertyName="IsExpanded" Value="{Binding ShowPreview, Mode=OneWay}"/>
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Path=Message1, Mode=OneWay}"></TextBlock>
</Expander>
//...
The binding is not lost when you manualy expand/collapse one Expander...
Do three things,
Make sure your ViewModel is implementing INotifyPropertyChanged. Your ui wont know about the change if your view model doesnt inform it when the property changes
Change the Mode to TwoWay, you want your view model updated when the expander changes and you want your expander updated when the view model changes
Lastly if the above two don't work use a debug converter to ascertain if your binding is failing. there is an example here of how to do this. This is a technique every wpf developer needs.
I know there was an issue with radio buttons that they would lose their bindings when another button in the group was set, i don't think that is the issue here, however a debug converter will help you figure this out.

Categories

Resources