I want my ComboBox to be disabled when my collection is null or empty and to be enabled when I update the collection and fill it, in the same way as my "Connect" button.
I tried IsEnabled="{Binding CanConnect }", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
I typed this with MVVM:
Model.cs
internal class PuertosCollection : ObservableCollection<Puerto>
{
}
internal class Puerto
{
public string Nombre { get; set; }
public SerialPort Valor { get; set; }
public override string ToString()
{
return Nombre;
}
}
ViewModel.cs
public bool CanConnect
{
get
{
return CurrentPuerto?.Valor.IsOpen != null;
}
}
private ICommand conectarCommand;
public ICommand ConectarCommand
{
get
{
if (conectarCommand == null)
conectarCommand = new RelayCommand(new Action(Conectar), () => CanConnect);
return conectarCommand;
}
}
private void Conectar()
{
currentPuerto.Valor.Open();
}
private Puerto currentPuerto;
public Puerto CurrentPuerto
{
get { return currentPuerto; }
set
{
currentPuerto = value;
RaisePropertyChanged("CurrentPuerto");
}
}
private PuertosCollection listaPuertos;
public PuertosCollection ListaPuertos
{
get { return listaPuertos; }
set
{
listaPuertos = value;
if (value != null && value.Count > 0)
{
CurrentPuerto = value[0];
}
RaisePropertyChanged("ListaPuertos");
}
}
private ICommand listarPuertosCommand;
public ICommand ListarPuertosCommand
{
get
{
if (listarPuertosCommand == null)
listarPuertosCommand = new RelayCommand(new Action(ListarPuertos));
return listarPuertosCommand;
}
}
private void ListarPuertos()
{
ListaPuertos = Generator.Puertos();
}
View.xaml
<Window.Resources>
<vm:ConfiguracionViewModel x:Key="ConfiguracionVM"/>
<vm:DatoViewModel x:Key="DatoVM"/>
</Window.Resources>
<DockPanel>
<StackPanel DataContext="{StaticResource ConfiguracionVM}" DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Left" Margin="8">
<Button Content="Conectar" Command="{Binding ConectarCommand}" Margin="0,0,8,0"/>
<Label Content="Puerto:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding CanConnect }" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Command="{Binding ListarPuertosCommand}" VerticalAlignment="Center" Margin="0,0,8,0">
<StackPanel>
<Image Source="/Recursos/Imagenes/actualizar_96px.png" Width="32" Height="32" />
</StackPanel>
</Button>
<Button VerticalAlignment="Center">
<StackPanel>
<Image Source="/Recursos/Imagenes/ajustes_48px.png" Width="32" Height="32"/>
</StackPanel>
</Button>
</StackPanel>
<StatusBar DockPanel.Dock="Bottom">
<Label Content="Statusbar"/>
</StatusBar>
<DataGrid DataContext="{StaticResource DatoVM}">
</DataGrid>
</DockPanel>
I am learning MVVM with this tutorial
This is very simple.
First create a Converter that receives an integer that corresponds to the amount of items you have in the collection, if the amount is greater than zero it will return a True, if not it will return a False.
using System;
using System.Windows.Data;
namespace MyProject
{
public class CountToBoolean : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value is int) ? ((int)value) > 0 : false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Then you create an instance of the Converter in the App.xaml or in the window you want to use it.
<cv:CountToBoolean x:Key="CountToBoolean"/>
And finally you apply it to the ComboBox:
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding ListaPuertos.Count, Converter={StaticResource CountToBoolean}}" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
That should do it.
I tried IsEnabled="{Binding CanConnect}", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
At least from your provided code, you do not trigger the property changed event for the CanConnect property when it is changed.
Disabling the combo box
You can add a style to the ComboBox that disables it if the ListaPuertos collection is null or empty.
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ListaPuertos}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding ListaPuertos.Count}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
Related
I am trying to create a banner with different states and colors to show messages to the user, showing a Border with a specific style and a Label with an style bases in the Border style and using DataTrigger. I have created each custom styles for each state in my App.xaml and I am trying to change the state based on a property of my ViewModel.
The problem is that the style doesn't change every time I change the property, but nevertheless if I modify some of the XAML during debugging, the style is refreshed correctly.
Maybe I am missing a NotifyPropertyChanged somewhere?
Visual example:
This is My code:
App.xaml with my defined styles and my custom converter
<Application.Resources>
<local:StyleConverter x:Key="StyleConverter" />
<Style x:Key="Banner" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="10" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style x:Key="Banner_Info" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#e3f7fc" />
<Setter Property="BorderBrush" Value="#8ed9f6" />
</Style>
<Style x:Key="Banner_Error" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#ffecec" />
<Setter Property="BorderBrush" Value="#f5aca6" />
</Style>
<Style x:Key="Banner_Success" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#e9ffd9" />
<Setter Property="BorderBrush" Value="#a6ca8a" />
</Style>
<Style x:Key="Banner_Text" TargetType="{x:Type Label}">
<Setter Property="FontWeight" Value="DemiBold"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#8ed9f6">
<Setter Property="Foreground" Value="#31708F" />
</DataTrigger>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#f5aca6">
<Setter Property="Foreground" Value="#B10009" />
</DataTrigger>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#a6ca8a">
<Setter Property="Foreground" Value="#529214" />
</DataTrigger>
</Style.Triggers>
</Style>
</Application.Resources>
Styleconverter.cs
public class StyleConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, CultureInfo culture) {
if (targetType != typeof (Style)) {
throw new InvalidOperationException ("The target must be a Style");
}
var styleProperty = parameter as string;
if (value == null || styleProperty == null) {
return null;
}
string styleValue = value.GetType ()
.GetProperty (styleProperty)
.GetValue (value, null)
.ToString ();
if (styleValue == null) {
return null;
}
Style newStyle = (Style) Application.Current.TryFindResource (styleValue);
return newStyle;
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException ();
}
}
Banner.cs
public class Banner : INotifyPropertyChanged {
private string _Message;
public string Message {
get => _Message;
private set {
_Message = value;
RaisePropertyChanged (null);
}
}
private string _Style = "Banner_Success";
public string Style {
get => _Style;
private set {
_Style = value;
RaisePropertyChanged (null);
}
}
public Banner SetSuccess (string message) {
Style = "Banner_Success";
Message = message;
return this;
}
public Banner SetInfo (string message) {
Style = "Banner_Info";
Message = message;
return this;
}
public Banner SetError (string message) {
Style = "Banner_Error";
Message = message;
return this;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string PropertyName) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (PropertyName));
}
#endregion
}
Mainwindow.xml
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="10*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" Style="{Binding ., Mode=TwoWay, Converter={StaticResource StyleConverter}, ConverterParameter=BannerStyle}">
<Label Grid.Column="1" Style="{StaticResource Banner_Text}" Content="{Binding BannerMessage}" />
</Border>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Padding="10" Margin="10" Content="BLUE" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="blue" />
<Button Padding="10" Margin="10" Content="RED" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="red" />
<Button Padding="10" Margin="10" Content="GREEN" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="green" />
</StackPanel>
</StackPanel>
</Grid>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged {
public Banner Banner { get; } = new Banner ();
public string BannerStyle => Banner.Style;
public string BannerMessage => Banner.Message;
public RelayCommand CmdChangeColor { get; }
public MainViewModel () {
Banner.SetSuccess ("This is the initial message");
CmdChangeColor = new RelayCommand (param => ChangeColor (param.ToString ()));
}
public void ChangeColor (string color) {
switch (color) {
case "blue":
Banner.SetInfo ("INFO!! This should be a banner with blue background");
break;
case "red":
Banner.SetError ("ERROR!! This should be a banner with red background");
break;
case "green":
Banner.SetSuccess ("SUCCESS!! This should be a banner with green background");
break;
}
RaisePropertyChanged (null);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string PropertyName) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (PropertyName));
}
#endregion
}
bind to property which has notifications, instead of entire view model ({Binding .}). This way also simplifies converter (no more reflection)
<Border Style="{Binding Path=Banner.Style, Converter={StaticResource StyleConverter}">
public class StyleConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, CultureInfo culture) {
if (targetType != typeof (Style)) {
throw new InvalidOperationException ("The target must be a Style");
}
string styleValue = value?.ToString();
if (styleValue == null) {
return null;
}
Style newStyle = (Style) Application.Current.TryFindResource (styleValue);
return newStyle;
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException ();
}
}
I have a custom ComboBox that have each item (Favorites and not favorites) is a Label + Button, then the last item have only a button to load all elements. Now I want to add a header as the first item, that says "Favorites".
Right now I have:
<ComboBox
x:Name="ComboBoxBtn"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,0,0,-1"
Width="300"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Path=Selected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Name="PART_GRID">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Width="250" Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}" />
<Button Name="PART_BUTTON"
Grid.Column="1"
Content="+"
Command="{Binding AddCommandButton, ElementName=root}"
CommandParameter="{Binding}"
Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}"/>
<Button Content="Carregar Todos" Margin="5,5"
Command="{Binding LoadAllCommandButton, ElementName=root}"
CommandParameter="{Binding ElementName=root, Path=FavoriteType}"
Visibility="{Binding Path=.,Converter={StaticResource elementToVisibilityForAddConverter}}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Favorite}"
Value="True">
<Setter TargetName="PART_GRID"
Property="Background"
Value="#FFE6E6FA" />
<Setter TargetName="PART_BUTTON"
Property="Content"
Value="-" />
<Setter TargetName="PART_BUTTON"
Property="Command"
Value="{Binding RemoveCommandButton, ElementName=root}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I preferred a different aproach, which i think is easier and more clean:
i created an empty interface IDrawable.
all classes i need to put inside the combobox should inherit from IDrawable
i created these:
MyLabel:
public class MyLabel : IDrawable
{
public string text { get; set; }
public MyLabel()
{
this.text = "MYTEXT";
}
}
MyButton:
public class MyButton : IDrawable
{
public string text { get; set; }
public MyButton()
{
this.text = "MYNBUTTON";
}
}
MyLabelButton:
public class MyLabelButton : IDrawable
{
public string labelText { get; set; }
public string buttonText { get; set; }
public MyLabelButton()
{
labelText = "labelText";
buttonText = "buttonText";
}
}
than here is the xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1">
<Window.Resources>
<DataTemplate DataType="{x:Type me:MyButton}">
<Button Content="{Binding text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabel}">
<TextBlock Text="{Binding text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabelButton}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding labelText}"/>
<Button Content="{Binding buttonText}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Name="MyGrid">
<ComboBox Name="MyCombo" ItemsSource="{Binding list}" SelectedItem="{Binding sel}" PreviewMouseLeftButtonUp="ComboBox_PreviewMouseLeftButtonUp"/>
</Grid>
</Window>
and codebehind:
public partial class MainWindow : Window
{
private IDrawable clicked;
public ObservableCollection<IDrawable> list { get; set; }
public IDrawable sel { get; set; }
public MainWindow()
{
InitializeComponent();
list = new ObservableCollection<IDrawable>();
list.Add(new MyLabel());
list.Add(new MyLabelButton());
list.Add(new MyButton());
this.DataContext = this;
}
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition(MyGrid);
clicked = null;
VisualTreeHelper.HitTest(
MyGrid,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
private HitTestResultBehavior ResultCallback(HitTestResult result)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(result.VisualHit);
if (parentObject == null)
return HitTestResultBehavior.Continue;
var v = parentObject as Button;
if (v == null)
return HitTestResultBehavior.Continue;
if (v.DataContext != null && v.DataContext is IDrawable)
{
clicked = (IDrawable)v.DataContext;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
}
the result is
as you can see I have standard combobox and custom elements. which i think it's better. in codebehind you can handle everything, like the impossibility of select the first label, call the command associated with the button, if the button is pressed, and so on.
In ComboBox_PreviewMouseLeftButtonUp i handled the click on the selected item, in case you want to do something particular if the selected button is pressed, and not show the dropdown menu.
this example is pretty barebones you need to customize it a little more and use MVVM everywhere.
at the moment you can push the button in the dropDown menu, you probabily want to disable the click if that buttont isn't selected.
EDIT
ComboBox_PreviewMouseLeftButtonUp should be like this:
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((ComboBox)sender);
clicked = null;
VisualTreeHelper.HitTest(
(ComboBox)sender,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
(replaced Mygrid with (ComboBox)sender
As you can see in the image above, I have three columns in listbox.
First Column is FirstName, Second Column is LastName and Third Column is Age.
When I hit Enter on the Age Column a new Person is added to the list. Which eventually reflects the changes in ListBox.
Problem:
When I Press Enter on Age Column I get a new Person added as expected. But the focus does not go to the next ListItem. No matter how many times I press Enter I never get focus to the items added Programmatically.
Sample:
I have created a sample project that reproduces the issue:
Download Sample Project
I have a ListBox as follows:
<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}">
<ListBox.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="CurrentItemGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
<TextBox Grid.Column="1" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" Margin="5,0"/>
<TextBox Grid.Column="2" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Margin="5,0" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
IEventAggregator eventAggregator;
public MainWindowViewModel(IEventAggregator _eventAggregator)
{
People = new ObservableCollection<Person>();
People.Add(new Person());
eventAggregator = _eventAggregator;
DeleteUnwantedOrderItemTransactionCommand = new RelayCommand(DeleteUnwantedOrderItemTransaction);
AddNewOrderItemTransactionCommand = new RelayCommand(AddNewOrderItemTransaction);
}
public RelayCommand DeleteUnwantedOrderItemTransactionCommand { get; set; }
public RelayCommand AddNewOrderItemTransactionCommand { get; set; }
private ObservableCollection<Person> _People;
public ObservableCollection<Person> People
{
get
{
return _People;
}
set
{
if (_People != value)
{
_People = value;
OnPropertyChanged("People");
}
}
}
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (_SelectedPerson != value)
{
_SelectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
}
protected void DeleteUnwantedOrderItemTransaction(object obj)
{
if (SelectedPerson.FirstName == "")
{
People.Remove(SelectedPerson);
}
if (People.Count == 0)
{
People.Add(new Person());
}
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
protected void AddNewOrderItemTransaction(object obj)
{
if (SelectedPerson == People.Last())
People.Add(new Person());
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In Code-Behind:
public partial class MainWindow : Window
{
IEventAggregator _eventAggregator = new EventAggregator();
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel(_eventAggregator);
_eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Subscribe(MoveToNextUIElement);
}
void MoveToNextUIElement(bool obj)
{
// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus != null)
{
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
The moment you fire your event you have added your element to your VM, but it might not have been bound and created by the View (ListBox) yet. Dispatching the focus request with a low priority might help. Also check if you can access the element by pressing TAB, so you know the traversal works.
elementWithFocus.Dispatcher.Invoke(() =>
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)),
DispatcherPriority.Input); // !
Virtualization of the ListBox Elements could also be an issue. You have to bring the added item into view.
cheeers.
As the title says, Iam trying to show/hide a TextBox in WPF without writing code in MainWindow.xaml.cs file.
Model:
public class Person
{
public string Comment { get; set; }
}
View:
<Window x:Class="PiedPiper.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="WPF" Height="400" Width="400">
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" Name="CommentTextBox"></TextBox>
</Grid>
ViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel(Person person)
{
Comment = person.Comment;
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So the TextBox should be hidden at start, but visible when checkbox is checked. Please help!
Thanks.
You can bind TextBox.Visiblity to CheckBox.IsChecked. If you want to toggle between Hidden and Visible then you need to either write custom IValueConverter or create simple Style.Trigger
<StackPanel>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Name="CommentTextBox">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=CommentCheckBox, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
if you want to toggle between Collapsed and Visible there is an easier way and you can use build in BooleanToVisibilityConverter
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
The simplest way is to write a custom "BooleanToHiddenVisibilityConverter" and use it (like dkozl said).
It's a really simple converter and it comes in handy in many situations. I think that every descent WPF application should have one.
public sealed class BooleanToHiddenVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = false;
if (value is bool)
{
bValue = (bool)value;
}
return (bValue) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
return (Visibility)value == Visibility.Visible;
}
return false;
}
}
And use it like dkozl said:
<StackPanel>
<StackPanel.Resources>
<BooleanToHiddenVisibilityConverter x:Key="BooleanToHiddenVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked,
Converter={StaticResource BooleanToHiddenVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
I want to change an icon based on an enum.
I've created a new viewmodel for my UserControl named CallControlViewModel
public class CallControlViewModel : BaseViewModel
{
private InputTypeEnum _inputTypeEnum;
public CallControlViewModel()
{
}
public InputTypeEnum InputType
{
get { return _inputTypeEnum; }
set
{
if (_inputTypeEnum != value)
{
_inputTypeEnum = value;
NotifyPropertyChanged("InputType");
}
}
}
}
This is the baseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify of Property Changed event
/// </summary>
/// <param name="propertyName"></param>
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is the enum
public enum InputTypeEnum
{
Empty = 0, Number = 1, Text = 2
}
Code behind usercontrol
public partial class CallControl : UserControl
{
private CallControlViewModel callControlViewModel;
public CallControl()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(CallControl_Loaded);
}
void CallControl_Loaded(object sender, RoutedEventArgs e)
{
callControlViewModel = new CallControlViewModel();
this.DataContext = callControlViewModel;
}
private void CallBox_TextChanged(object sender, TextChangedEventArgs e)
{
InputTypeEnum type = DecideInputType();
callControlViewModel.InputType = type;
}
private InputTypeEnum DecideInputType()
{
if(string.IsNullOrEmpty(CallBox.Text))
{
return InputTypeEnum.Empty;
}
if (IsNumeric(CallBox.Text))
{
return InputTypeEnum.Number;
}
return InputTypeEnum.Text;
}
And this is my Xaml:
<UserControl.Resources>
<Style x:Key="InputTypeIndicatorStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=NumberIndicator}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=NumberIndicator}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=TextIndicator}" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="NumberIndicator">
<Border x:Name="CallIconBorder" Width="35" BorderThickness="1,0,0,0" Background="#353535"
BorderBrush="#5d5d5d" MouseLeftButtonDown="CallIconBorder_MouseLeftButtonDown" Style="{StaticResource CallBorderStyle}" >
<Image StretchDirection="DownOnly" Margin="5" Source="/Image/call.png"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="TextIndicator">
<Border x:Name="SearchIconBorder" Width="35" >
<Image StretchDirection="DownOnly" Margin="5" Source="/Image/search.png"/>
</Border>
</DataTemplate>
</UserControl.Resources>
<DockPanel x:Name="CallControlDock" VerticalAlignment="Bottom" Background="{StaticResource LightGrey}" Height="30">
<ContentControl Style="{StaticResource InputTypeIndicatorStyle}" DockPanel.Dock="Right" HorizontalAlignment="Right" />
<Border x:Name="ClearIconBorder" DockPanel.Dock="Right" Width="20" Visibility="Hidden" VerticalAlignment="Center" Margin="5,0,5,0"
MouseDown="ClearIconBorder_MouseDown" Style="{StaticResource ClearIconStyle}" Opacity="0.5">
<Image StretchDirection="DownOnly" Source="/Image/close.png" HorizontalAlignment="Left"/>
</Border>
<spinners:ucSpinnerCogs x:Name="LoadSpinner" DockPanel.Dock="Right" HorizontalAlignment="Right" Visibility="Collapsed" />
<TextBox x:Name="CallBox" TextWrapping="Wrap" FontSize="14" FontFamily="Segoe UI Semibold" HorizontalAlignment="Stretch"
Foreground="{StaticResource AlmostWhite}" VerticalAlignment="Center"
GotFocus="CallBox_GotFocus" LostFocus="CallBox_LostFocus" TextChanged="CallBox_TextChanged" KeyDown="CallBox_KeyDown"
MouseRightButtonDown="CallBox_MouseRightButtonDown"
ContextMenu="{x:Null}">
</TextBox>
</DockPanel>
When I change the InputType property I get an error in the baseViewModel:
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); ==>
InvalidCastException, Can't convert object of type
MS.Internal.NamedObject to System.Windows.Datatemplate
What am I doing wrong?
For everyone who ran into the same issue but doesn't find another answers helpful in finding what exactly is going on.
The problem occurs when you try referencing resources with StaticResource before their declaration.
In this question these resources are NumberIndicator and TextIndicator.
It is happening because StaticResource works at compile-time and cannot look forward. So to solve the issue you can move the resouces to where they are not referenced yet. Or just use run-time DynamicResource.
Right, this whole post is one messy red herring... for anyone not familiar with that saying, it means that you can forget all about the error given because when this is done properly, you won't get that error... it's misleading.
So here we go... using your Trigger method:
First, here's an enum:
public enum TestEnum
{
None, One, Two, Three
}
Now the properties:
private TestEnum enumInstance = TestEnum.None;
public TestEnum EnumInstance
{
get { return enumInstance; }
set { enumInstance = value; NotifyPropertyChanged("EnumInstance"); }
}
private ObservableCollection<TestEnum> enumCollection =
new ObservableCollection<TestEnum>() { TestEnum.None, TestEnum.One,
TestEnum.Two, TestEnum.Three };
public ObservableCollection<TestEnum> EnumCollection
{
get { return enumCollection; }
set { enumCollection = value; NotifyPropertyChanged("EnumCollection"); }
}
Now the XAML:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Width="16" Height="16" Stretch="None" Margin="0,0,0,20">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding EnumInstance}" Value="One">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/Copy_16.png" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumInstance}" Value="Two">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/Edit_16.png" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumInstance}" Value="Three">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/CloseRed_16.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<ComboBox ItemsSource="{Binding EnumCollection}" SelectedItem="{Binding EnumInstance}" />
</StackPanel>
I trust that you can transfer this code to your project ok. One last thing to note... if you have any more enum values than this, you'd be better off creating an EnumToBoolImageSourceConverter and Binding with that instead.
I do remember this problem from a project some years ago. We had the same problem and added code to intercept that like so:
/// <summary>
/// Tests whether the object is the 'NamedObject'. This is placed into 'DataContext' sometimes by WPF as a dummy.
/// </summary>
public static bool IsNamedObject(this object obj)
{
return obj.GetType().FullName == "MS.Internal.NamedObject";
}
We posted several questions about this on forums but never really got an answer