I have a ListBox control populated with several ListBox items. Each item contains a "Proceed" button and a "Postpone" button. I would like to hide that ListBox item (presented as a row in my case) once the "Postpone" button is clicked. The code I have currently doesn't seem to have any effect.
XAML:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding PostponeClicked}" Value="1">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
C#:
private void PostponeThirdPartyUpdatesButton_Click(object sender, RoutedEventArgs e)
{
DataTrigger d = new DataTrigger();
d.Binding = new Binding("PostponeClicked");
d.Value = 1;
var context = ((FrameworkElement)sender).DataContext as Tuple<RegScan_ThirdParty.InstalledApplicationFromRegistryScan, RegScan_ThirdParty.ManifestRequiredApplication, RegScan_ThirdParty.RequiredApplicationState>;
Button ThirdPartyPostponeButton = sender as Button;
ThirdPartyPostponeButton.IsEnabled = false;
if (context != null)
{
RegScan_ThirdParty.registryApplicationPostponeWorkflow(context);
}
ThirdPartyPostponeButton.IsEnabled = true;
}
I had to address the same thing once. Each item in your list box should be an object. We'll call it MyObject for now, since I have no idea what your object type is. In the MyObject class, you'll put your Proceed and Postpone commands.
//ViewModelBase implements INotifyPropertyChanged, which allows us to call RaisePropertyChanged, and have the UI update
class MyObject : ViewModelBase
{
private bool isNotPostponed = true;
public bool IsNotPostponed
{
get { return isNotPostponed; }
set
{
isNotPostponed = value;
RaisePropertyChanged("IsNotPostponed");
}
}
private Command postponeCommand;
public Command PostponeCommand
{
get
{
if (postponeCommand == null)
postponeCommand = new Command(PostponeCommand);
return postponeCommand;
}
}
private void Postpone(object x)
{
IsNotPostponed = false;
}
//similar code for Proceed Command
}
Then in the viewmodel of the view that displays the listBox, create a List that you can bind to your listbox (or whatever collection you want to use). I called it MyObjectsList in the XAML below. (I'm not showing the ViewModel code where this object lives, but I assume you have code for binding to the ListBox.) Then in your ItemsControl.ItemTemplate, bind to each MyObject in your List.
<ItemsControl ItemsSource="{Binding MyObjectsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis"/>
</DataTemplate.Resources>
<StackPanel Visibility="{Binding IsNotPostponed, Converter={StaticResource boolToVis}}">
<Button Command="{Binding PostponeCommand}" Content="Postpone"/>
<Button Command="{Binding ProceedCommand}" Content="Proceed"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When Postpone is clicked, the command will execute Postpone(), which will set IsNotPostponed to false. On setting IsNotPostponed to false, RaisePropertyChanged tells the UI that IsNotPostponed changed (you need to implement the INotifyPropertyChanged interface.) Lastly, when the UI gets the change notification, it converts the bool to a Visibility. True => Visible, False => Collapsed.
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.
I made example where I change Content with ComboBoxMenu:
<Border Grid.Row="1" Style="{StaticResource InsideBorders}">
<ContentControl Content="{Binding ElementName=ComboBoxMenu, Path=SelectedItem}"/>
</Border>
But how do it the same thing for button? There I have no "SelectedItem" property.
I have two buttons with which I will change Content. Buttons are on different places not in one DockPanel or similar, so DataTemplate I think is not possible. Or?
In case of ComboBox you can set its ItemsSource but in case of button you need to associate button with object, based on which ContentControl adjusts its view. My suggestion is to create list of possible values and assign each one to associated button.
C#:
enum ContentControlViewModel
{
MainViewModel,
SearchViewModel
}
XAML:
<Button Tag="{x:Static local:ContentControlViewModel.MainViewModel}"/>
Then create style for all buttons which are supposed to change ContentControl's view.
<Style TargetType="Button">
<Setter Property="Command" Value="{Binding SetContentControlViewModel}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"/>
</Style>
Then in SetContentControlViewModel command all you need to do is to assign value to CurrentViewModel property, based on parameter. Your ContentControl binds to CurrentViewModel and adjusts view.
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
OnPropertyChanged();
}
}
public void SetViewModel(ContentControlViewModel viewModel)
{
switch (viewModel)
{
case ContentControlViewModel.MainViewModel:
CurrentViewModel = new MainViewModel();
break;
}
}
I've got some troubles with a custom control I need to create. I try to explain you my needs first
I need to have a combobox that permits to check more than one item at time (with checkbox) but I want it to be smart enought to bind to a specific type.
I've found some MultiSelectionComboBox but none reflects my need.
Btw my main problem is that I wish to have a generic class as
public class BaseClass<T> : BaseClass
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<T>), typeof(BaseClass<T>), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(BaseClass<T>.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int i = 0;
//MultiSelectComboBox control = (MultiSelectComboBox)d;
//control.DisplayInControl();
}
public IEnumerable<T> ItemsSource
{
get { return (IEnumerable<T>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
public class BaseClass : Control
{
}
and a more context specific item for example
public class MultiCurr : BaseClass<Currency>
{
static MultiCurr()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCurr), new FrameworkPropertyMetadata(typeof(MultiCurr)));
}
}
In my App.xaml I've defined a resource as
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding ItemsSource}" DisplayMemberPath="Description" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In my MainWindow I've created an object as
<Grid>
<local:MultiCurr x:Name="test" ItemsSource="{Binding Currencies}"></local:MultiCurr>
</Grid>
and the MainWindow.cs is defined as
public partial class MainWindow : Window, INotifyPropertyChanged
{
private IList currencies;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var lst = new List<Currency>();
for (int i = 0; i < 10; i++)
{
var curr = new Currency
{
ID = i,
Description = string.Format("Currency_{0}", i)
};
lst.Add(curr);
}
Currencies = lst;
}
public IList<Currency> Currencies
{
get
{
return this.currencies;
}
set
{
this.currencies = value;
NotifyPropertyChanged("Currencies");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here's the result ...
I was wondering what am I doing wrong? is it possible what am I tring to achieve?
Thanks
UPDATE #1:
I've seen that the main problem is the datacontext of the custom usercontrol
<Application.Resources>
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding **Currencies**}" DisplayMemberPath="{Binding **DisplayMemeberPath**}" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
If I put ItemsSource as Currency (which is a property of the MainWindow) it shows.
If I put ItemsSource and DisplayMemberPath (which are defined in the BaseClass no.. how can I set the context of the usercontrol to itself?)
UPDATE #2
I've added a GoogleDrive link to the project here if anyone wants to try the solution
Thanks
Combobox is not suitable control for multiselection, because it has given behaviour, that when yo select item, Combobox closes itself. That's why Combobox doest not have SelectionMode property like ListBox. I think that ListBox inside expander is what you need.
Generic Types are not a way to go. WPF handles this different, better way. Take listbox as an example. If you bind listbox.itemssource to generic observable collection, and you try to define e.g ItemTemplate, you get full intellisense when writing bindings and warning if you bind to not existing property. http://visualstudiomagazine.com/articles/2014/03/01/~/media/ECG/visualstudiomagazine/Images/2014/03/Figure8.ashx WPF designer automatically recognizes type parameter of your observable collection. Of cousre you need to specify type of datacontext in your page by using something like this: d:DataContext="{d:DesignInstance search:AdvancedSearchPageViewModel}". However your control dont have to be and shouldn't be aware of type of items.
Following example demonstrates control that meets your requirements:
<Expander>
<Expander.Header>
<ItemsControl ItemsSource="{Binding ElementName=PART_ListBox, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Mode=OneWay}" />
<Run Text=";" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander.Header>
<Expander.Content>
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple">
<ListBox.ItemsSource>
<x:Array Type="system:String">
<system:String>ABC</system:String>
<system:String>DEF</system:String>
<system:String>GHI</system:String>
<system:String>JKL</system:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</Expander.Content>
</Expander>
I reccomend you to create control derived from ListBox (not usercontrol).
I have hardcoded datatemplates, but you should expose them in your custom dependency properties and use TemplateBinding in you control template. Of course you need to modify expander so it looks like combobox and ListBoxItem style so it looks like CheckBox, but it is ease.
I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...
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}" ... />