Problem with ItemTemplate TextBlock - wpf

i am trying to make an item template where some of the field in my stack panel can be empty. When it's empty, I would like to set the visiblility to collapsed. I tried putting triggers but it doesn't seem to work and I am not very familiar with this part of WPF
Also, I would like to change the color of the background of this item when a specific value in my binding is true. Is it the same thing?
Thanks.

Using a ViewModel is one approach to solving this kind of problem.
The if your data was stored in an Item class you would make an ItemViewModel to wrap the Item for display in your items control. The ViewModel class would implement INotifyProperty changed in order to update the display and the setters would raise the PropertyChanged event passing the appropriate property name. You can also raise property changed events for as many interrelated changed fields as necessary.
Suppose you wanted Item.Description to display in a collapsed field when Description is empty. Your ViewModel properties could look like this
public string Description
{
get { return mItem.Description; }
set { mItem.Description = value; Notify("Description"); Notify("DescriptionVisibility"); }
}
public Visibility DescriptionVisibility
{
get { return string.IsNullOrEmpty(mItem.Description) ? Visibility.Visible : Visibility.Collapsed; }
}
In the XAML bind the text property to Description and the Visibility property to DescriptionVisibility.

If you want to hide an item if it's content is null, you have to redefine the ControlTemplate of its ListBoxItem (or ListViewItem or something else depending on which item container you're using) and use triggers that target the DataContext, like:
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
However, I'd suggest that you use the Filter delegate on your CollectionView to exclude your empty items from your view directly, to avoid collapsing unused items.
For example to exclude null objects, in your code behind, use:
CollectionViewSource.GetDefaultView(yourCollection).Filter = o => o != null;

Related

WPF: Prevent ListViewItem from being selected on Shift + Arrow

How do I prevent a ListViewItem from being selected when I click on an item and press (Down/Up) Arrow? I don't want to disable it, I just want it to not be selectable on Shift+Arrow. Some of my listviewitems need to be selectable and some others not.
Sample:
[ListViewItem 1]
[ListViewItem 2]
[ListViewItem 3]
User clicks on ListviewItem1, it gets selected.
User then presses SHIFT+DOWN ARROW,=> ListViewItem 2 is not selected.
At this point either ListViewItem3 is selected or it might take another SHIFT+DOWN ARROW to select ListViewItem3 (it doesn't matter as long as it gets selected sometime).
(In my project the actual ListViewItem 2 is actually NOT a regular item on 1 row, it is a vertical item that spans several rows in just one column. My ListView source has a compositecollection of very different items; I am trying to select what appears to be 'regular row items' only)
You can accomplish this by using a Property of your class such as IsSelected. Here is some sample code for what that Property would look like.
public bool IsSelected
{
get
{
return isSelected; //return the value of isSelected
}
set
{
if (isSelectable) //if the item is allowed to be selected
{ //then update the value of isSelected
isSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
In this example it is assumed that "isSelectable" is a value that is set in the constructor when your class is being initialized. If you don't already have INotifyPropertyChanged implemented, it needs to be... and the PropertyChanged event needs to be declared.
In addition, you will also want a Style that binds the selection of ListView items to this property. Here is an example of a Style that could accomplish this.
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.Resources>
You can take this one step further, and also bind to the Focusable Property. This will make it so that when you press SHIFT+DOWN it will skip any items that you cannot select and select the next item that is selectable. I would accomplish this in a manner similar to the following.
public bool Focusable
{
get
{
return isSelectable; //if the item is selectable, then it is focusable
}
}
You would also need to create a binding for this in your Style by creating an additional Setter. This could be done the following way, inside the same Style as before.
<Setter Property="Focusable" Value="{Binding Focusable}"/>
Try it both with and without that last Setter and see which implementation suits your needs best.
--------- UPDATE ---------
If you would like to only select items on mouse click, you could do so by subscribing to the ListView PreviewMouseLeftButtonDown event. Inside of the event handler, you would need to first get the clicked item, and call a function on your class which will override the selection. Here is an example of how that is done.
This function would exist in your UI code-behind and must be subscribed to by your ListView:
private void myListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
object obj = myListView.ItemContainerGenerator.ItemFromContainer(dep);
if (obj is MyClass)
{
foreach (MyClass i in myListView.Items)
i.OverrideSelect(false); //unselect all items
MyClass item = (MyClass)obj;
item.OverrideSelect(true); //select the item clicked
}
}
(Note: replace "MyClass" with your class type).
This function would exist in your class file:
public void OverrideSelect(bool selected)
{
isSelected = selected;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}

WPF TextBox: How to change binding mode default to OneWay?

Initially, I have the following code:
<TextBox Text="{Binding LengthUnit, Mode=OneWay}" IsReadOnly="True" Background="{x:Static SystemColors.ControlBrush}" />
I know I can define a style like this:
<Style TargetType="{x:Type TextBox}" x:Key="readOnlyTextBox">
<Setter Property="Background" Value="{x:Static SystemColors.ControlBrush}"></Setter>
<Setter Property="IsReadOnly" Value="True"></Setter>
</Style>
So that I can write:
<TextBox Text="{Binding LengthUnit, Mode=OneWay}" Style="{StaticResource readOnlyTextBox}" />
Because this textbox is readonly, the binding mode cannot be twoway. So, is it possible to make the OneWay binding as the default for my TextBox with this style?
EDIT: I need to change the binding mode to OneWay, because my property is get-only, not because I marked the TextBox readonly. However, I still want to change the default binding mode of the textbox to OneWay if possible.
this is the code I have following your suggestion, but it doesn't work. Did I miss anything?
public class ReadOnlyTextBox : TextBox
{
static ReadOnlyTextBox()
{
TextBox.TextProperty.OverrideMetadata(typeof(ReadOnlyTextBox),
new FrameworkPropertyMetadata() { BindsTwoWayByDefault = false, Journal = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit });
}
public ReadOnlyTextBox()
{
base.Background = SystemColors.ControlBrush;
base.IsReadOnly = true;
}
}
Because this textbox is readonly, the binding mode cannot be twoway.
Why not? IsReadOnly will prevent the user from modifying the Text and thereby modifying the property. Just make sure not to modify the Text property in code.
You can prevent the bound property from updating if you subclass TextBox. If you do so, you can override the TextBox.Text Dependency Property metadata.
public class TextBoxEx : TextBox
{
public TextBoxEx() : base() { }
static TextBoxEx()
{
TextBox.TextProperty.OverrideMetadata(typeof(TextBoxEx),
new FrameworkPropertyMetadata() { BindsTwoWayByDefault = false, Journal = true,
DefaultUpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.Explicit });
}
}
For some reasion changing BindsTwoWayByDefault to false doesn't work for me, but you can set DefaultUpdateSourceTrigger to Explicit which means that the bound property won't be updated unless done so by code, effectively making the binding OneWay.
Styles are a way to apply the same set of customizations to one or more properties for UI objects e.g. Background, IsReadOnly etc which are typically dependency properties.
Mode is a property of the Binding object, which is not a UI object.
You can set a style on any element
that derives from FrameworkElement or
FrameworkContentElement. -- Source (MSDN)
So this is not typically done via XAML/Styling.. my guess is you'd have to write code for it. Although XAML allows you to set nested properties Text.Mode="value", it is error prone (because it assumes that Text has been already set to a binding object). It will result in a binding exception if Text property returns an object that doesn't have a Mode property on it - e.g. if Text="a plain string".
If you absolutely must have this, then you'd need to create your binding programatically. You could use a naming convention for example to see if the backing property has a setter and add a OneWay binding if it doesn't.
I know this question is really old, but I recently encountered this problem myself, so maybe I can help somebody else as well.
I wanted to create a TextBox that have a OneWayBinding on its Text property.
I discovered that this is not working as shown in the question because WPF combines the existing metadata and the overriding metadata together by basically ORing the flags together.
Since BindsTwoWayByDefault is one of those flags, as long as one of the Metadata objects has BindsTwoWayByDefault=true is stays true.
The only way around that is to change the Metadata after the WPF merging process takes places in OverrideMetadata.
However the Metadata object is marked as Sealed in the method.
As any good developer would I stopped here and reconsidered...
Naaa, I used reflection to "unseal" the metadata object and set the BindsTwoWayByDefault back to false.
If anybody knows a better way to do that please let me know.
Here my code:
public partial class SelectableTextBlock : TextBox
{
static SelectableTextBlock()
{
var defaultMetadata = (FrameworkPropertyMetadata)TextProperty.GetMetadata(typeof(TextBox));
var newMetadata = new FrameworkPropertyMetadata(
defaultMetadata.DefaultValue,
FrameworkPropertyMetadataOptions.Journal,
defaultMetadata.PropertyChangedCallback,
defaultMetadata.CoerceValueCallback,
defaultMetadata.IsAnimationProhibited,
defaultMetadata.DefaultUpdateSourceTrigger);
TextProperty.OverrideMetadata(typeof(SelectableTextBlock), newMetadata);
//Workaround for a bug in WPF were the Metadata is merged wrongly and BindsTwoWayByDefault is always true
var sealedProperty = typeof(PropertyMetadata).GetProperty("Sealed", BindingFlags.Instance | BindingFlags.NonPublic);
sealedProperty.SetValue(newMetadata, false);
newMetadata.BindsTwoWayByDefault = false;
sealedProperty.SetValue(newMetadata, true);
}
public SelectableTextBlock()
{
InitializeComponent();
}
}

Multiselect WPF Listbox Make Multiple Selections With a Single Click

I have a databound multiselect listbox bound to a datatable. When I select a listboxitem I want some other listboxitems in the same listbox to be selected automatically. I want multiple items to be selected with a single click. How can i do that? I can't do it in SelectionChanged event because it leads to calling the same event again and breaks my logic altogether.
Please help. Any help will be highly appreciated.
UPDATE:
My listbox is already bound to a datatable which has a IsSelected column.I am using the value of this column in a style setter to make the listboxitem selected.Suppose i have 10 rows in the datatable.Now if the user selects the second listboxitem,i can get the isselected of the correspondong row in the database as 1.
But how can i get the other items to select at the same time? I think as Kent said,I rather use a property for binding. But how can i use a property to bind a listbox to a datatable?
Bind IsSelected to a property in your data class. When the property is changed, execute the logic to update the IsSelected property in other data objects:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Then in your data class you can have something like this:
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
UpdateOtherItems();
}
}
}
Or you could have the data item raise an IsSelectedChanged event and have the owning class manage the interdependencies of the selection.
Would having a "SelectedItem" property with the logic in the setter for that property that would handle selecting your other 'like' items?
That's perhaps the way I would go, hard to say with out more details.
I am working on a similar thing.
I have single select combo-boxes that I load with the Selected Value property from the database, and now I am working on multi-select list boxes for-which I have a list of selections in the database I need to bind to the selected list for my list box.
I don't see a way to do it without a loop.
I see listbox read/write properties for getting or setting the Items, SelectedItem/Index/Value, or read only properties for Items or SelectedItems.
Maybe this is cheating, but, when you are adding the items in the SelectionChanged event have you tried setting IsEnabled false while you selected the multiple items and then setting it back to true afterwords, I think that is supposed to keep the controls events from firing?
I have created a MultiSelectCollectionView that you might find useful here:
http://grokys.blogspot.com/2010/07/mvvm-and-multiple-selection-part-iii.html

How to delete a ListViewItem when bound to a DataView

How do I delete a selected ListViewItem from a WPF ListView when the ItemsSource is set to a DataView? I can get the ListViewItem that was selected and then how do remove the actual row in the DataView?
DataView dv = (DataView)myListView.ItemsSource;
ListViewItem lvi = (ListViewItem)myListView.ItemContainerGenerator.ContainerFromItem(myListView.SelectedItem);
<Delete ListViewItem here>
When you bind your collection to the listview, use ListCollectionView instead of DataView. Can be easily done like this (where dataView is of type DataView):
ListCollectionView lcv = new ListCollectionView(dataView);
myListView.ItemsSource = lcv;
Now when you need to delete any object, just do this:
ListCollectionView lcv = (ListCollectionView) myListView.ItemsSource;
lcv.Remove(myListView.SelectedItem);
And after deleting, just refresh the view:
lcv.Refresh();
or
((ListCollectionView)myListView.ItemsSource).Refresh();
Consider using the M-V-VM pattern to separate the notion of removing an item from your list of data objects and DIRECTLY removing them from your current UI implementation. The two do not need to know about each other, aside from Bindings.
When you use the MVVM pattern, expose a boolean "IsSelected" property in your ViewModel.
public class SimpleViewModel : BaseViewModel //For INotifyPropertyChanged, etc
{
public IList<SimpleBusinessObject> ViewModelItems;
public SimpleViewModel()
{
ViewModelItems = new ObservableList<SimpleBusinessObjectViewModel>();
}
}
public class SimpleBusinessObjectViewModel
{
public bool ViewModelIsSelected { get; set; }
public SimpleBusinessObjectViewModel()
{
ViewModelIsSelected = false;
}
}
Next, in your View try something like this:
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Setter Property="IsSelected" Value="{Binding ViewModelIsSelected}"
</Style.Triggers>
</Style>
<ListView ItemsSource={Binding ViewModelItems}>
//here you can insert how you want to display a ListViewItem
</ListView>
This will let you add, edit, and remove items in your ViewModel's List -- just like if it were the actual ListView. From here, you can also check each item's IsSelected (that responds to mouse interactions with the ListView) without actually checking the ListViewItem. This will be a much cleaner, maintainable solution.

Is there a way to logically group controls in WPF

Here's the scenario
I have a Grid with some TextBlock controls, each in a separate cell in the grid. Logically I want to be able to set the Visibility on them bound to a property in my ViewModel. But since they're each in a separate cell in the grid, I have to set each TextBlock's Visibility property.
Is there a way of having a non-visual group on which I can set common properties of its children? Or am I dreaming?
There is no non-visual group that would make this possible.
Setting the Visibility properties, directly or in a common Style shared by all of the TextBlocks, is probably the simplest solution.
Another option is to bind the visibility property of each item in your group of items to one single item, that way in your code behind you are only ever having to set the visibility of one item.
If possible I mostly place them in a GroupBox and set the groupbox BorderThickness to 0. That way all controls are grouped, you don't see that it's a groupbox and you can set the visibility with one property..
<Style TargetType="{x:Type GroupBox}"
x:Key="HiddenGroupBox">
<Setter Property="BorderThickness"
Value="0" />
I hope you have defined all of your cell UI elements inside a DataTemplate. You can do a small trick at the ViewModel level to achieve what you are looking for.
Have Singleton class at the ViewModel, which should have the Visibility or an equivalent property which you wanted to bind to every TextBlock.
The Singleton class should implement INotifypropertyChanged to get the change notification to the UI
Bind the Singleton property in the XAML and control this property from anywhere in your application.
< TextBlock Visibility="{Binding Source={x:Static local:Singleton.Instance},Path=Visibility}"
And a simple Singleton class can be implemented as
public class Singleton :INotifyPropertyChanged
{
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null){ instance = new Singleton(); }
return instance;
}
}
private Visibility _visibility;
public Visibility Visibility
{
get { return _visibility; }
set
{
_visibility = value;
PropertyChanged( this, new PropertyChangedEventArgs("Visibility") );
}
}
public event PropertyChangedEventHandler PropertyChanged;
private static Singleton instance;
}
Now you can control Singleton.Instance.Visibility = Visibility.Collapsed anywhere from your code behind
It may be possible to make a custom control that redirects all its add/remove children methods to its own parent, while still keeping a record of its contents so it can apply its own property styles. Would be tricky though.
I realise that this a very ancient question, but there will no doubt people finding this thread after searching for something related. Therefore I offer the following very simple solution:
Place all of the controls in question into a new grid that sits within the existing grid; spans the appropriate cells and replicates them within it's own structure. Then you can change the visibility of the new grid, and with it the controls inside.

Resources