ContentSelector WPF - wpf

I want to set a different content Template inside a button, to act like a Toggle Button, depending on the state of my ViewModel. I have simplified the code quite a bit, but in each Data Template the button's content draws different Path Geometries, together with a textblock.
ContentSelector class:
public class ContentSelector : DataTemplateSelector {
public DataTemplate TrueTemplate { get; set; }
public DataTemplate FalseTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if ((bool)item)
return TrueTemplate;
return FalseTemplate;
}
}
Edit: Added properties to the button to set its content, where 'State' isa boolean value in my ViewModel, and the converter is just returning 'T' or 'F', based on the State of my ViewModel
XAML:
<DataTemplate x:Key="HeaderTemplate">
<ContentPresenter>
<ContentPresenter.Content>
<DockPanel LastChildFill="False">
<Button Content="{Binding State, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ButtonContentConverter}}" Command="{Binding ToggleCommand}">>
<Button.ContentTemplateSelector>
<ContentSelector>
<local:ContentSelector.TrueTemplate>
<DataTemplate>
<TextBlock Text="T" ... />
</DataTemplate>
</local:ContentSelector.TrueTemplate>
<local:ContentSelector.FalseTemplate>
<DataTemplate>
<TextBlock Text="F" ... />
</DataTemplate>
</local:ContentSelector.FalseTemplate>
</ContentSelector>
</Button.ContentTemplateSelector>
</Button>
</DockPanel>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
I cannot seem to hit the breakpoint in my ContentSelector class. It does work in the designer, meaning the button's content changes to 'T' or 'F' if I change it to return the corresponding DataTemplate in the ContentSelector Class, but when I run the application it never hits the ContentSelector, and the content of the button is completely empty.
Any ideas as to why this would happen? I have tried setting the Content of the button to the State of my ViewModel, and changing that to a boolean value using a converter, thinking that the content of the button must be set to trigger the ContentSelector, but it didnt work.

Related

Binding not working inside a <ItemsControl.ItemTemplate>

I can not get the binding of a text property for a DataTemplate in MVVM design pattern.
To show the problem I expose below a simplification of my problem, where I bind two different view properties to the same model property (aka AnObject.Text).
My code in MainWindow.xaml is:
...
<Button Grid.Row="0" Content="{Binding ButtonText}" />
...
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Label Content="aaaaa" />
<TextBlock Text="{Binding ItemText}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
...
My code behind in MainWindow.xaml.cs (which sets the same DataContext for Button and every item in <ItemsControl ItemsSource>):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
My code in MainWindowViewModel.cs is:
...
public ObservableCollection<object> MyItems => MyConverter.GetCollection(MyData.List);
public string ItemText => "dddd"; // this DOES works
public string ItemText => AnObject.Text; // this does NOT work
...
public string ButtonText => AnObject.Text; // this DOES works (note, same object property!)
...
Any idea why my binding inside the DataTemplate does not work?
Thanks in advance!
There are various things to understand here:
Button control will have the DataContext set to MainWindowViewModel instance. This is the reason why ButtonText variable value is getting reflected in Button control text.
For ItemsControl the DataContext is the the same as for the Button, i.e. the MainWindowViewModel instance.
Each item in the ItemsControl ItemsSource acts as a DataContext for the elements in the ItemTemplate, i.e. the DockPanel and its child elements. This is managed automatically by the framework. So essentially you will need a public property named ItemText in the class which will act as a DataContext for Dockpanel.
In your case the ItemText property is not the part of the objects which are in list.

User Control is not updating in window (WPF)

There is a UserControl which contains the bindings like below.
<TextBox Margin="5" Padding="0" IsReadOnly="True" Background="Transparent" BorderThickness="0" TextWrapping="Wrap" IsTabStop="False" FontSize="{DynamicResource TitleFontSize}" Text="{Binding ErrorTitle, Mode=OneWay}" />
it is bound by stack panel with name of GenericErrorControl and binding is as
<Visibility="{Binding IsShown, Mode=OneWay, Converter={StaticResource BoolToVis}, FallbackValue=Collapsed}">
Above control is added to one of view as below.
<views:GenericErrorControl Grid.Row="8" DataContext="{Binding GenericErrorControl, Mode=OneWay}" VerticalAlignment="Top/>
The problem is that user control is not appearing in the window after. In my viewmodel creating object i'm setting the value of IsShown but its not appearing. Kindly help and let me know if any other details needed.
It means that the binding is failing ie the fallback value is used from above code.
Things to do :
Ensure that your ViewModel inherits from the base class BindableBase ie it somehow implements the INotifyPropertyChanged interface and on property change, the PropertyChanged event is fired.
ie You have something similar to this in your view model.
private bool _IsShown;
public bool IsShown
{
get { return _IsShown; }
set { SetProperty(ref _IsShown, value); }
}
Double check your converter or post code here.

Expander inside ListBox not showing content when expanded the first time from binding

I have a dialog window that contains a ListBox whose ItemTemplate contains an expander.
Its IsExpanded is bound to a property in the item view model. The ListBoxItem's IsSelected property is also bound to the IsExpanded property in the item view model object. And finally the SelectedItem property of the ListBox is bound to a property with the same name in the view model.
The problem here is that when setting up the view model before showing the dialog and setting it to the DataContext of the dialog, the item in the listbox gets selected as it should, the expander arrow shows that it is in the expanded state, but the content of the expander is not displayed.
If I set up the view model after showing the dialog, eg. in the Loaded handler of the dialog things work as expected. What is going on here, and what would be the best way to fix it?
The dialog window is defined as:
<Window x:Class="WpfApplication1.Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
Title="Dialog" Height="300" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="Expander" x:Name="MyExpander" IsExpanded="{Binding IsExpanded, Mode=TwoWay}">
<Rectangle Width="100" Height="20" Fill="Red" />
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
And the ViewModel (implementation not included for the sake of brevity):
public interface IMyViewModel : INotifyPropertyChanged
{
object SelectedItem { get; set; }
ObservableCollection<IMyItemViewModel> Items { get; }
}
public interface IMyItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
}
Then I have a simple main window with a button, and its Click handler is defined as:
private void Button_Click(object sender, RoutedEventArgs e)
{
MyViewModel vm = new MyViewModel();
MyItemViewModel item = new MyItemViewModel();
vm.Items.Add(item);
vm.SelectedItem = item;
Dialog dialog = new Dialog();
dialog.DataContext = vm;
dialog.ShowDialog();
}
When I run the application and click the button, the dialog shows up, the expander arrow indicates that it is in the expanded state, but its content is not displayed. Clicking on the expander collapses it, and clicking it again expands it, this time showing the content.
Putting the same code directly in the Main Window instead of a dialog however works as it is supposed to.
If I just do a Dispatcher.BeginInvoke(new Action(() => vm.SelectedItem = item); instead of setting it directly things also seem to work, but this feels a bit shaky.
What can be done to fix this problem?
Sounds like the content of the expander is not measured again after it is loaded, if the IsExpanded property is already set to true. Or to put it another way, the content is measured when it has still no actual size.
I suppose the easiest solution would be to just set the SelectedItem once the dialog has been loaded:
dialog.Loaded += (s, x) => vm.SelectedItem = item;

ToggleButton group: Ensuring one item is always selected in a ListBox

I am trying to duplicate the left/center/right alignment toolbar buttons in Word. When you click the "Left Alignment" button the Center and Right buttons uncheck. I am using a WPF ListBox with ToggleButtons.
The problem is the user can click the Left Alignment button twice. The second click causes the button to uncheck and sets the underlying value to null. I'd like the second click to do nothing.
Ideas? Force the ListBox to always have one selected item? Prevent the null in the view model (need to refresh the ToggleButton binding)?
<ListBox ItemsSource="{x:Static domain:FieldAlignment.All}" SelectedValue="{Binding Focused.FieldAlignment}">
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}">
<TextBlock Text="{Binding Description}" />
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
yeah, i would prefer the radiobutton too for this case, but if you want to use togglebutton then maybe you can bind the isenabled property to ischecked so it cannot be cliked when it's checked
create custom control from ToggleButton,
in *.xaml.cs file, declare and define control
public class ToggleButton2 : ToggleButton
{
public bool IsNotCheckable
{
get { return (bool)GetValue(IsNotCheckableProperty); }
set { SetValue(IsNotCheckableProperty, value); }
}
// Using a DependencyProperty as the backing store for IsNotCheckable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsNotCheckableProperty =
DependencyProperty.Register("IsNotCheckable", typeof(bool), typeof(ToggleButton2), new FrameworkPropertyMetadata((object)false));
protected override void OnToggle()
{
if(!IsNotCheckable)
{
base.OnToggle();
}
}
}
in *.xaml, replace ToggleButton with my:ToggleButton2, then you can bind IsNotCheckable to IsChecked, just like below,
<my:ToggleButton2 IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}" IsNotCheckable="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked, Mode=OneWay}">
Rather than implementing this as ToggleButtons, I would use RadioButtons with custom templates. It would probably save you a lot of headache.

WPF: ComboBoxes in ListBox and concurrency

I have code like this:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>Some Other Stuff Here</TextBlock>
<ComboBox ItemsSource="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is, every time the outside ListBox.SelectedItem gets changed, the ComboBoxes inside it would change their SelectedIndex to -1. Which means if I click "Some Other Stuff Here" (unless the ListBoxItem it is in is selected), all the comboboxes' selection get cleared.
How do I overcome this? Thx!
Presumably your combobox is bound to something like an ObservableCollection - try exposing an instance of ICollectionView instead:
class DataSource
{
// ...
public ObservableCollection<string> MyData { get; private set; }
public ICollectionView MyDataView
{
get
{
return CollectionViewSource.GetDefaultView(this.MyData);
}
}
}
You can then bind your combobox with:
<ComboBox ItemsSource="{Binding MyDataView}" IsSynchronizedWithCurrentItem="True" />
This means that the 'selected item' for each data source is stored in the ICollectionView object instead of within the combobox, which should mean that it is persisted when the ListBox SelectedItem changes

Resources