I have a databound WPF comboxbox where I am using the SelectedValuePath property to select a selected value based on something other than the object's text. This is probably best explained with an example:
<ComboBox ItemsSource="{Binding Path=Items}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=SelectedItemId}"/>
The datacontext for this thing looks like this:
DataContext = new MyDataContext
{
Items = {
new DataItem{ Name = "Jim", Id = 1 },
new DataItem{ Name = "Bob", Id = 2 },
},
SelectedItemId = -1,
};
This is all well and good when I'm displaying pre-populated data, where the SelectedItemId matches up with a valid Item.Id.
The problem is, in the new item case, where the SelectedItemId is unknown. What WPF does is show the combo box as blank. I do not want this. I want to disallow blank items in the combo box; I would like it to display the first item in the list.
Is this possible? I could write some code to explicitly go and set the SelectedItemId beforehand, but it doesn't seem right to have to change my data model because of a shortcoming in the UI.
I think you are going to have to do some manual work here to get this behavior. You could check in code behind when you first display the ComboBox whether or not the SelectedItemId matches up or not and then change the selected index based on that. Or if you know that the SelectedItemId will always be -1 when there is no corresponding item, you could use a datatrigger.
Method 1:
if (!DataContext.Items.Exists(l => l.Id == DataContext.SelectedItemId))
{
MyComboBox.SelectedIndex = 0; //this selects the first item in the list
}
Method 2:
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItemId}" Value="-1">
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
you may use this style trigger: if selecteditem is null,
first element is selected.
<Trigger Property="SelectedItem" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="0"/>
</Trigger>
Related
I am trying to make the items of my WPF comboBox visible (preferably greyed out) but not allow the user to select them using c# code.
I have tried the following:
comboBoxName.IsHitTestVisible = false
comboBoxName.Focusable = false
However, this prevents them seeing the contents of the comboBox all together. How would I make the contents visible but cannot be selected in c#?
Set an item container style that will disable each ComboBoxItem.
<ComboBox x:Name="comboBoxName" ItemsSource="{Binding Collection}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="False"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
If you use code-behind, can define and apply the item container style like this.
var itemContainerStyle = new Style(typeof(ComboBoxItem));
var isEnabledSetter = new Setter(IsEnabledProperty, false);
itemContainerStyle.Setters.Add(isEnabledSetter);
comboBoxName.ItemContainerStyle = itemContainerStyle;
I am trying to implement a simple search feature in SQL and wpf... I need to highlight only part of the next in results that has been entered in a TextBox.
So I do the search in SQL get the results and put them on a TreeViewItem that adds them to a TreeView. That all works but how would i go around making only a certain part of TreeViewItem Header bold?
I already know how to find where the part of the text I want bold is but i just need to make it bold.
TreeViewItem root_item = new TreeViewItem() { Header = "Users" };
FoundUsersTreeView.Items.Add(root_item);
while (sqlReader.Read())
{
TreeViewItem new_item = new TreeViewItem() { Header = sqlReader.GetString(0) };
root_item.Items.Add(new_item);
}
If you're going to use wpf for any substantial work, you should use MVVM.
Working directly with controls is OK for very simple stuff but the difficulty ramps up very quickly.
You should Bind data and template that into controls instead.
This is how WPF is intended to be used.
Work with the base data rather than controls.
If you take a look at this sample:
https://gallery.technet.microsoft.com/WPF-Highlight-Matching-71ad5a04
It works with a listview rather than treeview and highlights text by making the background red.
Not exactly what you need.
But the principle is the thing to look at.
You could easily change the listview used to a treeview.
Having said that, you only seem to have on level of items in your treeview, so you pretty much might as well use a listview anyhow.
Anyhow.
If you take a look, you'll see each item is split up using a regex so there's a separate RunHi viewmodel for each piece.
Matching pieces have IsMatch set to true on their RunHi.
These are templated into a horizontal listview - so it looks like one textblock but is in fact one or more arranged one after the other.
Each row of the listview is itself a listview.
You could of course make the header of each treeviewitem a listview if you particularly want a treeview.
A datatrigger is used to make the background yellow.
To make it bold instead you can just change that part:
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMatch}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</ControlTemplate>
You can achieve this using HeaderTemplate of your TreeViewItem. As you're adding TreeViewItem from code behind, you can use the Tag property to differentiate which one need to be set as Bold.
You can add below DataTemplate to your Resources.
DataTemplate
<DataTemplate x:Key="headerTemplate">
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource AncestorType=TreeViewItem, Mode=FindAncestor}}"
Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
Code Behind
TreeViewItem root_item = new TreeViewItem() { Header = "Users" };
treeView.Items.Add(root_item);
while (sqlReader.Read())
{
var new_item = new TreeViewItem { Header = sqlReader.GetString(0), Tag = true };
new_item.HeaderTemplate = Resources["headerTemplate"] as DataTemplate;
root_item.Items.Add(new_item);
}
You can set boolean value to Tag property to make that Node as Bold.
I have a non-editable combobox (SupplierDropdown), and I would like when the user chooses the last value on the list, the combobox should become editable, with no value and automatically focused ready for the user to type their value. In other words, an editable, blank box with a blinking cursor ready for input.
Here's the code I have:
private void SupplierDropdown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SupplierDropdown.SelectedIndex == SupplierDropdown.Items.Count - 1)
{
SupplierDropdown.IsEditable = true;
SupplierDropdown.Text = "";
SupplierDropdown.Focus();
}
}
However, although the combobox indeed becomes editable, the rest of the code doesn't seem to work: (a) the combobox doesn't clear the value the user chose, and (b) the combobox doesn't get the focus, the user needs to hit tab once to see the blinking cursor in the combobox.
How can I make this work?
This seems like a major pain, because the control just is not intended to be used that way. Even if you get the initial logic to work, it is difficult to turn the editable flag back off in a sound way. When the user enters text that partially matches a given option it will select said option. How do you know whether that selection was caused by accident or intentionally and thus whether the editable flag should be set to false again?
Would recommend the additional TextBox scheme, where you just show a TextBox for any custom values, can be done in XAML only (if you know what value to trigger on) but that's not necessary.
<ComboBox Name="cb" SelectedValuePath="Content">
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>Other</ComboBoxItem>
</ComboBox>
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue, ElementName=cb}" Value="Other">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
(PS: This is an example of using the item/value instead of the index, even if someone decides that the "other" option should be at the top instead, the code will still work as intended, yours would not.)
I'm trying out WPF and I have an application with an ObservableCollection hooked up to a ListView. It adds the items perfectly and everything is falling together. It is taking in data from a live source and so item values are getting updated and rows are getting added. But now I want the most recent rows to be highlighted, text changed, something to show that this certain row is being changed. So I set up a data trigger and added a value to the DataType in the ObservableCollection called RecentlyChanged. If its true, then set the text to red, otherwise text is black.
When I run this code the list items change to red and then never change back and I have tried everything and it is ticking me off. I've checked the debugger to and even when the value is no (I'm using strings yes and no because i wanted to try all sorts of data types) it stays red. Code for the data trigger is below:
--Edit: Added in the second datatrigger that i tried using before to no avail.
<ListView ItemsSource="{Binding DataTable, UpdateSourceTrigger=PropertyChanged}">
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RecentlyChanged}" Value="yes">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=RecentlyChanged}" Value="no">
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
The issue is that the ObservableCollection only reports changes to the collection itself, in other words, it fires a CollectionChanged event whenever items are added or removed, but not when properties of those items change. In order to achieve the desired result - updating a data trigger when an item's property changes - the item itself must implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the desired property is set.
In this case, you can use the following:
using System.ComponentModel;
public class ListViewItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var propChanged = PropertyChanged;
if(propChanged != null)
{
propChanged(this, new PropertyChangedEventArgs(name));
}
}
private string recentlyChanged = "yes"; // Recently changed on creation
public string RecentlyChanged
{
get { return recentlyChanged; }
set {
recentlyChanged = value;
OnPropertyChanged("RecentlyChanged");
}
}
// ... define the rest of the class as usual
}
WPF magic should take care of the rest.
I have used the following approach to bind IsSelected of my items to a Property: WPF ListView Programmatically Select Item
<ListView.ItemContainerStyle>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</ListView.ItemContainerStyle>
Now I am able to select my items in code behind by simple setting the IsSelected property to true. However I am not able to deselect items by setting the IsSelected property of my items to false.
Setting the items property IsSelected to true will trigger the ListViewSelectionChanged event. However setting the property IsSelected of an already selected item to false does not trigger the event. The property will be changed to false but the item remains selected within the ListView. I have also tried using Mode=TwoWay without any success.
I would appreciate any sort of help!
Thank you very much in advance,
Thomas
For OP or others looking to "programmatically" deselect a ListView.
If your ListView rigged up as Single, Extended or Multiple you can always just:
YourlistView.Selecteditem = null;
Or you can use this as well:
YourlistView.UnselectAll();
Looks like you are just missing the TargetType for the style. Add the target type of ListViewItem as per Kent's original code below.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>
</Style>
</ListView.ItemContainerStyle>