I need to bind the IsEnabled property of a set of ListBoxItems to a bool property value that resides in the DataContext. I have followed several tutorials to get to where I am however I am still not having any luck. In my XAML I have defined a setter within a ListBox.ItemContainerStyle as follows:
<ListBox Name="Requests">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsEnabled"
Value="{Binding IsEnabled}"/>
The bool property value resides in a class set as the DataContext as such:
public class dcSystemRequests : INotifyPropertyChanged
{
private bool _IsEnabled;
public bool IsEnabled
{
get
{
return _IsEnabled;
}
set
{
if (_IsEnabled != value)
{
_IsEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyChanged)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyChanged));
}
}
Now when I modify the property I am not seeing the value reflected in the UI as expected; the property value is being changed in the code like this:
((dcSystemRequests)DataContext).IsEnabled = !((dcSystemRequests)DataContext).IsEnabled;
Since this is proprietary software I only included what I think is necessary to understand the issue but will happily provide more if needed. Any advice or guidance is greatly appreciated.
If the IsEnabled property is part of the ListBox's DataContext, then you need to use a RelativeSource binding:
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled"
Value="{Binding DataContext.IsEnabled, RelativeSource={RelativeSource AncestorType=ListBox}"/>
</Style>
The DataContext of the ListBoxItems is each corresponding Data Item.
See ItemsControl for more information.
Your ItemContainerStyle has not the same DataContext as your ListBox, but the data of the ListBox's items. Therefore binding to the IsEnabled property makes no sense if you do not set the binding Source to the ListBoxItem's parent.
Related
I have two combobox bind to the same ObservableCollection proprety, i would like to know if is possible to disable an item in combox if it's already selected in one of them ? in wpf. thx
You can bind the IsSelected property of the ComboBox item to a bool identifying a selected state in your class.
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected" Value="{Binding SelectedA, Mode=OneWayToSource}"></Setter>
<Setter Property="IsEnabled" Value="{Binding SelectedB}"></Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected" Value="{Binding SelectedB, Mode=OneWayToSource}"></Setter>
<Setter Property="IsEnabled" Value="{Binding SelectedA}"></Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Create a class which exposes a couple of bools
public class MyClass : INotifyPropertyChanged
{
private bool selectedA;
public bool SelectedA
{
get { return !selectedA; }
set { selectedA = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedA")); }
}
private bool selectedB;
public bool SelectedB
{
get { return !selectedB; }
set { selectedB = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedB")); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
(In the example I am simply reversing each selected bool in the getter, but in reality flipping the bool would probably be best performed using a converter)
You can either:
Set the IsEnabled property of the ComboBoxItem using something like this: Disallow/Block selection of disabled combobox item in wpf (Thanks Kamil for the link)
Make it look different (but still selectable);
Update the second list so it removes the selected option when the selection of the first changes; or
Apply validation after the fact (e.g. show an error icon/message, or disable the "submit" button if the two selections are the same).
Your choice will depend only on the experience you're trying to achieve.
Is it possible to base the value property of a trigger to a property of an element?
For instance, in a ControlTemplate that has a ScrollBar, I'm trying to set its Visibility property to Collapsed if its Minimum and Maximum properties are equal.
However, the following doesn't work because you can't set a Binding as the Value of a Trigger because a Trigger is not a DependencyObject.
<Trigger Property="Minimum"
SourceName="PART_ScrollBar"
Value="{Binding Maximum, SourceName=PART_ScrollBar}">
<Setter Property="Visibility"
TargetName="PART_ScrollBar"
Value="Collapsed" />
</Trigger>
So can this be done purely with triggers, or do I have to do this in code-behind?
In this scenario, Id recommend creating a custom behavior
Like this
public class MinMaxVisibilityBehavior : Behavior<ScrollBar>
{
public override void OnAttached()
{
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MaximumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MinimumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
}
private void CheckMinMax(object sender, EventArgs e)
{
AssociatedObject.Visibility = AssociatedObject.Minimum ==
AssociatedObject.Maximum ? Visibility.Hidden : Visibility.Visible;
}
}
and then in your XAML
<ScrollBar>
........
<i:Interaction.Behaviors>
<local:MinMaxVisibilityBehavior />
</i:Interaction.Behaviors>
</ScrollBar>
I'm having a problem implementing a search functionality in my TreeView on a WPF Project.
I used this guide to create a TreeView with ViewModels. I have used the TextSeachDemo and edited the Controls in a way that they that they fit my application. (Added the right Classes, more layers etc.)
Everything works fine, I get a structure with correct children and parents and the search function also works, as it finds the correct entry.
Problem now is: When i try to set the "IsExpanded" Property from code nothing happens. Debubgging shows me that the PropertyChanged event in the RaiseProperty Changed Method is always null.
In the test Project provided by Josh Smith, everything seems to be working fine.
The only significant difference that i could make out is that he set the datacontext in code while i did in the XAML:
Code from Josh Smith:
public TextSearchDemoControl()
{
InitializeComponent();
// Get raw family tree data from a database.
Person rootPerson = Database.GetFamilyTree();
// Create UI-friendly wrappers around the
// raw data objects (i.e. the view-model).
_familyTree = new FamilyTreeViewModel(rootPerson);
// Let the UI bind to the view-model.
base.DataContext = _familyTree;
}
From the constructor from the MainViewModel (The ViewModel that handles the entire window)
List<FactoryItem> rootItems = _machineService.GetFactoryItems();
FactoryTree = new FactoryTreeViewModel(rootItems);
Where as FactoryTree is a public Observable Property which i bind the DataContext of the TreeView too (instead of in code as above):
<TreeView DataContext="{Binding FactoryTree}" ItemsSource="{Binding FirstGeneration}">
The other way around by the way, when i open a item via the GUI, the breakpoint on my property does trigger and raise an event.
Any ideas?
This solution addresses the problem in a more mvvm friendly way.
A UserControl contains a TreeView.
It uses the type YourViewModel as data context.
The view model contains a collection of YourDomainType which itself has a child collection ChildElements of the same type.
In xaml, the data is bound to the ElementInViewModel collection of the view model. In addition there is a HierarchicalDataTemplate (which is appropriate for a tree view).
The type YourDomainType contains properties IsExpanded and IsSelected which are bound to the respective properties of the TreeViewItem in a Style.
If you set these properties in your view model the tree view should react as expected (selecting or expanding the respective TreeViewItem).
I am aware that IsExpanded and IsSelected do not belong to a DTO object.
YourDomainType is probably more a type for displaying data, but it could also wrap a DTO object stored in it.
<UserControl>
<UserControl.DataContext>
<YourViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource Source="{Binding Path=ElementsInViewModel}" x:Key="Cvs">
</CollectionViewSource>
<HierarchicalDataTemplate DataType="{x:Type DomainModel:YourDomainType}"
ItemsSource="{Binding Path=ChildElements}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</Setter>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</Setter>
</Style>
</UserControl.Resources>
<DockPanel>
<TreeView ItemsSource="{Binding Source={StaticResource Cvs}}"/>
</DockPanel>
</UserControl>
public class YourViewModel
{
public ObservableCollection<YourDomainType> ElementsInViewModel
{
get
{
return _elementsInViewModel;
}
set
{
if (_elementsInViewModel != value)
{
_elementsInViewModel = value;
RaisePropertyChanged("ElementsInViewModel");
}
}
}
ObservableCollection<YourDomainType> _elementsInViewModel;
public YourViewModel()
{
}
}
public class YourDomainType
{
public ObservableCollection<YourDomainType> ChildElements
{
get
{
return _childElements;
}
set
{
if (_childElements != value)
{
_childElements = value;
RaisePropertyChanged("ChildElements");
}
}
}
ObservableCollection<YourDomainType> _childElements;
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (_isExpanded != value)
{
_isExpanded = value;
RaisePropertyChanged("IsExpanded");
}
}
}
bool _isExpanded;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
bool _isSelected;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
string _name;
}
I have extended the TreeViewItem class to allow me to store extra data within a tree view item. I would like to be able to set the style of the treeview item based on the value of one of the extended properties I have added.
So far I have:
namespace GX3GUIControls
{
public class GX3TreeViewItem : TreeViewItem
{
public bool Archived { get; set; }
public object Value { get; set; }
}
}
<src:GX3ClientPlugin.Resources>
<Style TargetType="{x:Type Controls:GX3TreeViewItem}">
<Style.Triggers>
<DataTrigger Archived="True">
<Setter Property="Background" Value="Gray" />
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
</Style.Triggers>
</Style>
</src:GX3ClientPlugin.Resources>
But I get the error - Error 1 The property 'Archived' was not found in type 'DataTrigger
DataTrigger has no Archived property, but you can bind your Achived-property to it via the Binding property like so <DataTrigger Binding="{Binding Path=Archived}" Value="True">
To notify your view if the Achived property changes, you could either:
1.Implement the INotifyPropertyChanged Interface in your GX3TreeViewItem-class: public class GX3TreeViewItem : TreeViewItem, INotifyPropertyChanged, create a method which raises the PropertyChanged Event:
private void PropertyChanged(string prop)
{
if( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(prop);
}
}
and place this method in the setter of your property:
private bool _achived;
public bool Achived
{
get
{
return _achived;
}
set
{
_achived = value;
PropertyChanged("Achived");
}
}
2.Or make your property a DependencyProperty.
Honestly it seems like you're doing it wrong. Those properties should be on your data.
You can do something like this,
Style="{Binding Path=Archived, Converter={StaticResource GetStyle}}"
GetStyle is an IValueConverter, no need to extend TreeView imo.
This is not the correct way to implement this. you should take a look at the MVVM Pattern.
Your UI is not the proper place to "store extra data". UI is UI and data is data. This is the worst mistake done by people coming from a winforms or otherwise non-WPF background, using a wrong approach and a wrong mindset in WPF.
This will either not work (because the ItemContainerGenerator of the TreeView knows nothing about your class, or require extra work in overriding the default behavior of such class.
I have a ListView Contained in a UserControl I would like to disabled a button when no items are selected in the UserControl, would it be the right way to do it? So far, it doesn't disable, it just stays enable all the way.
I've included the xaml code.
searchAccountUserControl is the UserControl name property in the xaml.
And AccountListView is the ListView name property in the userControl xaml.
<Button Content="Debit" IsEnabled="true" HorizontalAlignment="Left" Margin="18,175,0,0" Name="DebitButton" Width="128" Grid.Column="1" Height="32" VerticalAlignment="Top" Click="DebitButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=searchAccountUserControl.AccountListView, Path=SelectedValue}" Value="{x:Null}" >
<Setter Property="Button.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Thanks.
Finally i've used :
in my ViewModel :
private bool _isSelected;
public bool IsSelected { get { return _isSelected; }
set { _isSelected = _account.View.CurrentItem != null;
PropertyChanged.SetPropertyAndRaiseEvent(this, ref _isSelected, value,
ReflectionUtility.GetPropertyName(() => IsSelected)); } }
And then Use isEnabled = "{Binding Path=IsSelected}" in the xaml.
There are a few things wrong here.
Precedence, if you set IsEnabled on the control itself the style will never be able to change it.
ElementName, it's an ElementName, not a path, just one string that gives the name of one element. Everything beyond that goes into the Path.
Style syntax, if you set a Style.TargetType you should not set the Setter.Property with a type prefix (although leaving it does not break the setter).
By the way, this alone is enough:
<Button IsEnabled="{Binding SelectedItems.Count, ElementName=lv}" ...
It's obvious that you aren't using Commanding (ICommand Interface). You should either use that (and preferably the Model-View-ViewModel architecture).
But, if you want to stick with code-behind and XAML:
<ListView SelectionChanged="AccountListView_SelectionChanged" ... />
private void AccountListView_SelectionChanged(Object sender, SelectionChangedEventArgs args)
{
DebitButton.IsEnabled = (sender != null);
//etc ...
}
More information on MVVM: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
You need to set the DataContext of the View (UserControl) to the instance of the ViewModel you want to use. Then, from there, you can bind to properties on the ViewModel, including ICommands. You can either use RelayCommand (see link above) or use Commanding provided by a framework (for example, Prism provides a DelegateCommand). These commands take an Action (Execute) and a Func (CanExecute). Simply provide the logic in your CanExecute. Of course, you'd also have to have your ListView SelectedItem (or SelectedValue) be databound to a property on your ViewModel so you can check to see if it's null within your CanExecute function.
Assuming you use RelayCommand you don't have to explicitly call the RaiseCanExecuteChanged of an ICommand.
public class MyViewModel : ViewModelBase //Implements INotifyPropertyChanged
{
public MyViewModel()
{
DoSomethingCommand = new RelayCommand(DoSomething, CanDoSomething);
}
public ObservableCollection<Object> MyItems { get; set; }
public Object SelectedItem { get; set; }
public RelayCommand DoSomethingCommand { get; set; }
public void DoSomething() { }
public Boolean CanDoSomething() { return (SelectedItem != null); }
}
<ListView ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}" ... />
<Button Command="{Binding DoSomethingCommand}" ... />