I am using Editable Combo box
<ComboBox Width="200" Height="30" IsEditable="True">
<ComboBoxItem Content="true"/>
<ComboBoxItem Content="false"/>
</ComboBox>
1st issue: If i select true and then delete the last character 'e' then the text box has just tru but the selected Item property change is never fired, I mean the setter of the property data bind to selected item is never called.
2nd issue: If i now open the drop down and try to select true, the text in the text box remains same 'tru' it does not change to true
Vikas
You can "adjust" the behaviour e.g. by using an attached property like this:
The behaviour will be: If the text from the Text property is different from the selected item's text => set selected index to -1 (this also makes selected item null etc.). Tweak to your preferences.
Note: I'm not sure if this works correctly if you bind a value to Enable and change this several times (memory leaks etc.). It's also hardwired to string items. You might need a more general apporach to be really reusable.
public class StrictComboxBox
{
public static readonly DependencyProperty EnableProperty = DependencyProperty.RegisterAttached(
"Enable", typeof (bool), typeof (StrictComboxBox), new PropertyMetadata(defaultValue: default(bool), propertyChangedCallback: EnableChanged));
private static void EnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var cb = d as ComboBox;
if (cb == null)
return;
var value = GetEnable(cb);
if (value)
{
DependencyPropertyDescriptor
.FromProperty(ComboBox.TextProperty, typeof(ComboBox))
.AddValueChanged(cb, TextChanged);
}
else
{
DependencyPropertyDescriptor
.FromProperty(ComboBox.TextProperty, typeof(ComboBox))
.RemoveValueChanged(cb, TextChanged);
}
}
private static void TextChanged(object sender, EventArgs e)
{
var cb = sender as ComboBox;
var selectedTextMatches = cb.SelectedValue != null && ( (cb.SelectedValue as ComboBoxItem).Content as string) == cb.Text;
if (selectedTextMatches == false)
{
cb.SelectedIndex = -1;
}
}
public static void SetEnable(DependencyObject element, bool value)
{
element.SetValue(EnableProperty, value);
}
public static bool GetEnable(DependencyObject element)
{
return (bool) element.GetValue(EnableProperty);
}
}
Usage in xaml would be:
<Window xmlns:my ="clr-namespace:YourNameSpace.ContainingTheStrictComboBoxClass" ...>
<ComboBox Width="200" Height="30" IsEditable="True" my:StrictComboxBox.Enable="True">
<ComboBoxItem Content="true"/>
<ComboBoxItem Content="false"/>
</ComboBox>
Related
I have a UserControl that contains a ListBox and I want to track the SelectedItems of that listbox.
The UserControl has a DP "SelectedItemsList" that is defined like this
public static DependencyProperty SelectedItemsListProperty = DependencyProperty.Register(
"SelectedItemsList",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null,
OnSelectedItemsChanged));
In the listbox' Item "SelectionChanged" event, I want to save the selected items to the DP. This is triggered whenever I change the selection in the listbox.
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = this.myListBox.SelectedItems;
}
In my view that contains the "MyListControl" I create a binding to my viewmodel that want to use the selected items.
<controls:MyListControl
Source="{Binding SomeItemsList, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding SelectedItems, UpdateSourceTrigger=PropertyChanged}"/>
My problem is, that the DP SelectedItemsList never gets updated. The PropertyChangeCallback "OnSelectedItemsChanged" of the DP is only triggered when I initially load the lists content. The value of the SelectedItemsList is always null.
I am aware that this question is similar to Dependency property callback does not work, but the answers posted there do not solve my problem.
What am I missing here?
Thanks,
Edit (2015-09-10):
Thank you all for your comments. I found a solution that fits my needs:
First of all I created a custom listbox control that provided the list of selected items in a dependency property (very similar to Select multiple items from a DataGrid in an MVVM WPF project).
public class CustomListBox : ListBox
{
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof (IList),
typeof (CustomListBox),
new PropertyMetadata(null));
public CustomListBox()
{
SelectionChanged += OnSelectionChanged;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList= new ArrayList(this.SelectedItems);
}
}
I am not happy yet with the "new ArrayList"-part, but if in my viewmodel's property setter I want to check for equality, SelectedItemsList can not be a reference of SelectedItems. The previous and the new value would always be the same.
Then I reduced the item selection parts of my UserControl "MyListControl" simply to the dependency property itself:
public static DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null));
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
and modified the xaml of the MyListControl:
<controls:CustomListBox
SelectionMode="Extended"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=Source, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=SelectedItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
The property in my ViewModel looks like
public IList SelectedObjects
{
get { return _selectedObjects; }
set { if (this._selectedObjects != value)
{
this._selectedObjects = value;
OnPropertyChanged(SelectedObjectsProperty);
}
}
}
It was important that the type of this property is IList, otherwise the value in the setter would always be null.
And in the view's xaml
<controls:MyListControl
Source="{Binding CurrentImageList, UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding SelectedObjects, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
I just had the same problem today, unfortunately, when you are assigning to SelectedItemsList a value, WPF seems to unbind it. To fix it, I update the value in the binded item. I know that it is not the best solution in the world but for me it works.
In this case the code would looked like this:
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SetPropertyValue(
this.GetBindingExpression(SelectedItemsListProperty),
this.myListBox.SelectedItems);
}
private void SetPropertyValue(BindingExpression bindingExpression, object value)
{
string path = bindingExpression.ParentBinding.Path.Path;
var properties = new Queue<string>(
path.Split(
new[]
{
'.'
}).ToList());
this.SetPropertyValue(bindingExpression.DataItem, bindingExpression.DataItem.GetType(), properties, value);
}
private void SetPropertyValue(object destination, Type type, Queue<string> properties, object value)
{
PropertyInfo property = type.GetProperty(properties.Dequeue());
if (property != null && destination != null)
{
if (properties.Count > 0)
{
this.SetPropertyValue(property.GetValue(destination), property.PropertyType, properties, value);
}
else
{
property.SetValue(destination, value);
}
}
}
You need to bind your Listbox' SelectedItems to the DP SelectedItemsList to propagate the user selection to the DP. The binding you already have will then pass the changes on to the viewmodel, but I think you will need a binding mode 'twoway' instead of UpdateSourceTrigger.
And don't use the PropertyChangeCallback in your DP: Changing the SelectedItemsList if the SelectedItemsListProperty has changed makes no sense. (Usually the former is a wrapper property of the latter.)
Why is this failing?
I have a UserControl which contains a ComboBox.
I have a property called "Mandatory" with which that user can add validation in case no choice is made.
So I have this in the user control:
public static DependencyProperty MandatoryProperty
= DependencyProperty.Register("Mandatory", typeof(bool), typeof(CountyPicker),
new PropertyMetadata((bool)false, OnChangedMandatoryByBinding));
private static void OnChangedMandatoryByBinding
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var value = (bool)e.NewValue;
((CountyPicker)d).OnChangedMandatoryByBinding(value);
}
public bool Mandatory
{
get { return (bool)GetValue(MandatoryProperty); }
set { SetValue(MandatoryProperty, value); }
}
public void OnChangedMandatoryByBinding(bool value)
{
var binding = BindingOperations.GetBinding
(TheComboBox, ComboBox.SelectedItemProperty);
binding.ValidationRules.Clear();
if (value == true) // strange that I need this without getting a warning
{
binding.ValidationRules.Add(new NotNullValidator());
}
}
The xaml contains:
<UserControl x:Class="Foo.Controls.Pickers.CountyPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ComboBox Name="TheComboBox"
KeyDown="TheComboBox_KeyDown"
SelectedItem="{Binding County,Mode=TwoWay}" />
</UserControl>
But although the value of Mandatory is set like this:
<p:CountyPicker Mandatory="True"
CountyCode="{Binding customer.CountyCode, Mode=TwoWay}"/>
...no validation seems to be made. I have put a breakpoint in the NotNullValidator:s Validate method, but thats not hit.
I have a listview. I have set following int that :-
<ListView KeyboardNavigation.TabNavigation="Local" SelectionMode="Extended">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</Style>
</ListView.ItemContainerStyle>
One column in listview contains TextBox's.
If I set the UpdateSourceTrigger=LostFocus
in my textbox, I can not tab through the listview...Instead if I set UpdateSourceTrigger=Explicit, the tabbing is working...but source will not get updated.
Please help me
EDIT
public class TextBoxBehavior
{
#region Attached Property EscapeClearsText
public static readonly DependencyProperty EscapeClearsTextProperty
= DependencyProperty.RegisterAttached("EscapeClearsText", typeof(bool), typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEscapeClearsTextChanged)));
private static void OnEscapeClearsTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.KeyUp -= TextBoxKeyUp;
textBox.KeyUp += TextBoxKeyUp;
}
}
}
private static void TextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
//((DataContext<string>)((TextBox)sender).GetBindingExpression(TextBox.TextProperty).DataItem).RollbackChanges();
((TextBox)sender).Text = string.Empty;
}
else if (e.Key == Key.Enter)
{
((TextBox)sender).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
public static void SetEscapeClearsText(DependencyObject dependencyObject, bool escapeClearsText)
{
if (!ReferenceEquals(null, dependencyObject))
dependencyObject.SetValue(EscapeClearsTextProperty, escapeClearsText);
}
public static bool GetEscapeClearsText(DependencyObject dependencyObject)
{
if (!ReferenceEquals(null, dependencyObject))
return (bool)dependencyObject.GetValue(EscapeClearsTextProperty);
return false;
}
#endregion Attached Property EscapeClearsText
}
Below is the listview/gridview column which has the attached property in it.
<GridViewColumn Width="60">
<GridViewColumnHeader Content="Priority"
Command="{Binding Path=SortSelectedClaimCodeGroupsCommand}"
CommandParameter="Item.IntPriority">
</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border DataContext="{Binding Item.Priority}"
Style="{StaticResource ValidationResultBorderStyle}" HorizontalAlignment="Left" >
<TextBox Width="200" MaxLength="25" Text="{Binding Path=Value,Mode=TwoWay,
UpdateSourceTrigger=Explicit}" local:TextBoxBehavior.EscapeClearsText="True" >
When you set the UpdateSourceTrigger as explicit, you have to update the source by explicitly calling the method UpdateSource on your BindingExpression. Where is the code for that?
EDIT
In your TextBoxKeyUp event you are overwriting your Binding by setting the text on the press of Escape key. Firstly you bind it to the property Value and later you are explicitly setting the Textbox text property to String.Empty.This way text property will loose it's binding. So, later whenever you call UpdateSource it won't propagate to Source value since it's no longer binded to the Text property of textbox. Instead you should set the text like this -
((TextBox)sender).SetCurrentValue(TextBox.TextProperty, String.Empty);
This way your binding will be preserved and UpdateSource would work as it should.
I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!
I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.
I have a combo box defined as such
<ComboBox Name="RoomDropDown" Visibility="{Binding Path=RoomDropDownVisible,Mode=OneWay,Converter={StaticResource BoolVisibilityConvertor}}"
ItemsSource="{Binding Path=RoomList,Mode=OneWay}" DisplayMemberPath="display" SelectedValuePath="display" SelectedValue="{Binding Path=Room,Mode=TwoWay}"/>
There are 2 properties defined in the ViewModel, RoomList which is List and the Room property which is a string.
First time when i run the app everything works fine, and the Drop Down gets the correct values as well as the correct values is selected. However on a certain conditions the RoomList property is changed to a different source & the Room property is also changed. The problem that is now happening is the Combo Box is showing the correct values but the selected value is not getting selected. Worse, we can live with that, but the setter is also not firing when the value is manually changed in the DropDown.
Any pointers on what is going wrong here?
Followup:
Don't think I managed to get the exact problem across, here is some sample code that I wanted to add to illustrate the problem:
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel VerticalAlignment="Center" Width="100">
<ComboBox Name="TestBox" Height="20" Width="100" ItemsSource="{Binding Path=ComboSource}" DisplayMemberPath="display" SelectedValuePath="code"
SelectedValue="{Binding Path=ComboSelection,Mode=TwoWay}"/>
<Button Content="Click Here" Click="Button_Click" />
</StackPanel>
</Grid>
public MainPage()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
var temp = new List<Binding>();
temp.Add(new Binding() { code = "1", display = "One" });
temp.Add(new Binding() { code = "2", display = "Two" });
this.ComboSource = temp;
this.ComboSelection = "1";
this.DataContext = this;
};
}
private static readonly DependencyProperty ComboSelectionProperty =
DependencyProperty.Register("ComboSelectionProperty", typeof(string), typeof(MainPage), new PropertyMetadata(null));
public string ComboSelection
{
get { return (string)GetValue(ComboSelectionProperty); }
set
{
SetValue(ComboSelectionProperty, value);
this.RaisePropertyChanged("ComboSelection");
}
}
private static readonly DependencyProperty ComboSourceProperty =
DependencyProperty.Register("ComboSourceProperty", typeof(List<Binding>), typeof(MainPage), new PropertyMetadata(null));
public List<Binding> ComboSource
{
get
{
return (List<Binding>)GetValue(ComboSourceProperty);
}
set
{
SetValue(ComboSourceProperty, value);
this.RaisePropertyChanged("ComboSource");
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var temp = new List<Binding>();
temp.Add(new Binding() { code = "3", display = "Three" });
temp.Add(new Binding() { code = "4", display = "Four" });
this.ComboSource = temp;
this.ComboSelection = "3";
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class Binding
{
public string code {get; set;}
public string display { get; set; }
}
Not strictly MVVM, but to explain the problem, when the button click event is fired, the Combosource is changed with a new selection being made, however that selection does not bind and the problem i mentioned above starts happening.
Your SelectedValuePath is "display", which I assume is a string property of the Room class. But you're binding SelectedValue to the Room property of your viewmodel, and I assume this property is of type Room... So the SelectedValue is of the type string, and you're binding it to a property of type Room: it can't work because there is no conversion between those types.
Instead of using the SelectedValue property, why not use the SelectedItem ?
<ComboBox Name="RoomDropDown" Visibility="{Binding Path=RoomDropDownVisible,Mode=OneWay,Converter={StaticResource BoolVisibilityConvertor}}"
ItemsSource="{Binding Path=RoomList,Mode=OneWay}" DisplayMemberPath="display" SelectedItem="{Binding Path=Room,Mode=TwoWay}"/>
There seems to be a bug in ComboBox data binding where the binding will completely break if the data it is binding to SelectedValue becomes null.
Place a breakpoint in the ComboSelection setter and see if it is ever getting set to null. If this is the source of the problem, add this to your setter:
public string ComboSelection
{
// .....
set
{
if(value == null)
return;
// .....
}
}
On a side note, you probably don't need use a dependency property to back ComboSelection. The data binding for this should work just fine on a normal property as long as you keep using PropertyChanged.