Blacklight is an older set of WPF controls and styles. The code can be found here. It contains a control called AnimatedExpander which isn't really an expander, rather it just implements HeaderedContentControl and adds an IsExpandedProperty dprop:
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register("IsExpanded", typeof(bool), typeof(AnimatedExpander), new PropertyMetadata(true));
public bool IsExpanded
{
get
{
if (this.expandToggleButton != null)
{
return this.expandToggleButton.IsChecked.Value;
}
return (bool)GetValue(IsExpandedProperty);
}
set
{
SetValue(IsExpandedProperty, value);
}
}
I need to bind to IsExpanded so that I can persist whether expanders are expanded. I'm pretty sure I have the binding setup correctly, and that there is a problem with this custom dependency property. If I open the view in Snoop, and set the IsExpanded=True on the expander, the binding works. However, just clicking the expandToggleButton on the control only expands the control, it doesn't hit my binding.
<controls:AnimatedExpander IsExpanded="{Binding SGGExpanderExpanded}" />
private bool _sGGExpanderExpanded;
public bool SGGExpanderExpanded
{
get { return _sGGExpanderExpanded; }
set
{
if (_sGGExpanderExpanded != value)
{
_sGGExpanderExpanded = value;
OnPropertyChanged("SGGExpanderExpanded");
}
}
}
How can I bind to a value that changes when the user clicks the toggle button that is wired to expand the control?
A bad solution:
I was able to make this work by attaching an event to the ToggleButton click and looking at the "sender" Content and IsChecked values to update my viewmodel.
Related
I am creating a ToggleSwitchItem user control, which contains a ToggleSwitch and a TextBlock. I have defined a dependency property called IsChecked which I just want to use to expose the IsChecked property of the private ToggleSwitch child.
But the data binding doesn't work... It just stays at the default value when loaded.
What am I missing?
Code:
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(bool),
typeof(ToggleSwitchItem),
new PropertyMetadata(new PropertyChangedCallback
(OnIsCheckedChanged)));
public bool IsChecked
{
get
{
return (bool)GetValue(IsCheckedProperty);
}
set
{
SetValue(IsCheckedProperty, value);
}
}
private static void OnIsCheckedChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ToggleSwitchItem item = (ToggleSwitchItem)d;
bool newValue = (bool)e.NewValue;
item.m_switch.IsChecked = newValue;
}
for the data binding, I'm using to following:
<phone:PhoneApplicationPage.Resources>
<myApp:SharedPreferences x:Key="appSettings"/>
</phone:PhoneApplicationPage.Resources>
IsChecked="{Binding Source={StaticResource appSettings},
Path=SomeProperty, Mode=TwoWay}"
The SharedPreferences class is working fine, as it works without issue when bound to a plain vanilla ToggleSwitch's IsChecked property exactly as per above.
Thanks!
SOLUTION (with help from Anthony):
I bind my child toggle switch to my user control in the user control's constructor like so:
Binding binding = new Binding();
binding.Source = this;
binding.Path = new PropertyPath("IsChecked");
binding.Mode = BindingMode.TwoWay;
m_switch.SetBinding(ToggleSwitch.IsCheckedProperty, binding);
And I remove the callback as I no longer need it:
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(bool),
typeof(ToggleSwitchItem),
null);
public bool IsChecked
{
get
{
return (bool)GetValue(IsCheckedProperty);
}
set
{
SetValue(IsCheckedProperty, value);
}
}
I can't quite see what is actually wrong with the code you've show so far, except that you haven't show how the user toggling the switch would actually cause the IsChecked property to change.
Have you try using binding inside the UserControl:
<ToggleButton IsChecked="{Binding Parent.IsChecked, ElementName=LayoutRoot, Mode=TwoWay}" />
You do not need the OnPropertyChanged callback with this approach.
Check the DataContext of your control.Which means 2 things : All instances of your control must have right DataContext to work -ok-, and also you should not 'break' this DataContext when you define the control (at the Class level). If, when you define your control, you set the DataContext to 'this' / Me in code or to 'Self' in xaml, it nows refer only to itself and forget about the DataContext in which it is when you instanciate it in your application -- Binding fails.
If you have to refer to your control's properties within your control Xaml, use a binding with findAncestor / AncestorType = ToggleSwitchItem Or name your control in Xaml and bind with its ElementName.
Maybe this could help
public bool IsChecked
{
get { return GetValue(IsCheckedProperty) is bool ? (bool) GetValue(IsCheckedProperty) : false; }
set
{
SetValue(IsCheckedProperty, value);
}
}
I've got a UserControl with an ItemsSource property. As the base UserControl class does not implement ItemsSource, I had to create my own dependency property like this:
#region ItemsSource Dependency Property
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MonthViewControl),
new PropertyMetadata(OnItemsSourceChanged));
static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as MonthViewControl).OnItemsSourceChanged(e);
}
private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
RefreshLayout();
}
public IEnumerable ItemsSource
{
get
{
return (base.GetValue(ItemsSourceProperty) as IEnumerable);
}
set
{
base.SetValue(ItemsSourceProperty, value);
}
}
#endregion
Now in my ViewModel I have an Events property which is an ICollectionView of EventItem items like so:
private ObservableCollection<Controls.EventCalendar.EventItem> eventItems;
private CollectionViewSource events;
public System.ComponentModel.ICollectionView Events
{
get
{
if (events == null)
{
events = new CollectionViewSource();
events.Source = eventItems;
}
return events.View;
}
}
The issue I'm facing is that in my View, when I bind to the Events property, and I add an Item to eventItems, the UserControl won't fire the ItemsSourceChanged event and hence not update the UI.
For the sake of testing I added a simple listbox to the view which also binds to the Events property. That works like a charm. Updates to eventItems observableCollection are reflected in the ListBox.
I'm figuring it has something to do with my ItemsSource dependency property. Maybe I would need to use a Custom Control which inherits form ItemsControl instead of a UserControl?
To help you understand my problem: I'm trying to create a calendar like control which shows events/agenda entries (similar to Google Calendar). It works like a charm. The UI is updated when the control is resized. The only thing that's left is the automagical update once the ItemsSource changes.
Hope someone can help.
EDIT: The moment I posted I realized that the event can't be fired as the ItemsSource property does not change. It is the underlying collection that changes. However, I'm not how to handle that. What do I need to implement to make this work. Just a hint would be enough. I don't need every implementation details.
Opening the PresentationFramework.dll within Reflector and looking at System.Windows.Controls.ItemsControl showed the following:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ItemsControl), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(ItemsControl.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControl control = (ItemsControl) d;
IEnumerable oldValue = (IEnumerable) e.OldValue;
IEnumerable newValue = (IEnumerable) e.NewValue;
ItemValueStorageField.ClearValue(d);
if ((e.NewValue == null) && !BindingOperations.IsDataBound(d, ItemsSourceProperty))
{
control.Items.ClearItemsSource();
}
else
{
control.Items.SetItemsSource(newValue);
}
control.OnItemsSourceChanged(oldValue, newValue);
}
Not knowing what RefreshLayout does my hunch is that it has something to do with the way the ObservableCollection<T> is being wrapped as the above code is oblivious to what the concrete collection type is and it would therefore be handled by the type being wrapped; in this case an ObservableCollection<T> Try modifying your property as seen below to return the default view and adjust your ItemsSource property to be more akin to the above code from the framework and work backwards from there.
private ObservableCollection<Controls.EventCalendar.EventItem> eventItems;
private ICollectionview eventsView;
public System.ComponentModel.ICollectionView Events
{
get
{
if (eventsView == null)
eventsView = CollectionViewSource.GetDefaultView(eventItems);
return eventsView;
}
}
I have a ComboBox hosted in a TabItem. When I select an item from the ComboBox, an appropriate ListView is populated. When I navigate away from the TabItem and then return, the SelectedItem in the ComboBox is empty, but the ListView remains populated correctly. The SelectedItemChanged event has not been raised.
Why is the selected item not shown in the ComboBox when I return to view it?
Some code:
In the view ---
<ComboBox x:Name="customersComboBox"
ItemsSource="{Binding Path=Customers }"
SelectedItem="{Binding Path=SelectedCustomer, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Code"
IsEditable="False">
In the ViewModel -
public ICustomerInfo SelectedCustomer
{
get { return (ICustomerInfo)GetValue(SelectedCustomerProperty); }
set { SetValue(SelectedCustomerProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedCustomer. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedCustomerProperty =
DependencyProperty.Register("SelectedCustomer", typeof(ICustomerInfo), typeof(OrdersViewModel), new UIPropertyMetadata(null, SelectedCustomerChanged));
private static void SelectedCustomerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d==null)
{
return;
}
OrdersViewModel viewModel = d as OrdersViewModel;
if (e.NewValue == null)
{
return;
}
ICustomerInfo selectedCustomer = e.NewValue as ICustomerInfo;
viewModel.SelectedCustomerChanged(selectedCustomer);
}
private void SelectedCustomerChanged(ICustomerInfo selectedCustomer)
{
if (selectedCustomer != null)
{
if (!GetOrders())
{
return;
}
}
}
I've worked out what was happening.
When the Tab loses focus, the SelectedItemChanged event IS raised! I think I understand the mechanism that causes the event to be raised, but I don't understand why it needs to happen - apparently it is "by design".
What was happening is that the e.NewValue was null and my code did not change to the new values but the SelectedItem WAS set to null.
Programming error but the strange behaviour of the TabItem (and its child controls) had me flumoxed!
set EnabledViewState=true
then post some code, so that I can recognize the error.
I have a list of ToggleButtons being used as the ItemTemplate in a ListBox similar to this answer using the MultiSelect mode of the Listbox. However I need to make sure at least one item is always selected.
I can get the proper behavior from the ListBox by just adding an item back into the ListBox's SelectedItems collection on the ListBox.SelectionChanged event but my ToggleButton still moves out of its toggled state so I think I need to stop it earlier in the process.
I would like to do it without setting IsEnabled="False" on the last button Selected because I'd prefer to stay with the Enabled visual style without having to redo my button templates. Any ideas?
You can override the OnToggle method to prevent toggling the state, by not calling the base implementation :
public class LockableToggleButton : ToggleButton
{
protected override void OnToggle()
{
if (!LockToggle)
{
base.OnToggle();
}
}
public bool LockToggle
{
get { return (bool)GetValue(LockToggleProperty); }
set { SetValue(LockToggleProperty, value); }
}
// Using a DependencyProperty as the backing store for LockToggle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LockToggleProperty =
DependencyProperty.Register("LockToggle", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
}
Have you tried using RadioButtons instead? It normally can't be deselected without selecting another one. It can also be styled to look like a ToggleButton:
<RadioButton Style="{StaticResource {x:Type ToggleButton}}"/>
Or, if you already have a Style for it, just make it BasedOn="{x:Type ToggleButton}". Note that the Visual Studio Editor shows an error in the first case, but it compiles and works fine.
This is hackey, but if you don't want custom code you could always use the property "IsHitTestVisible", when you don't want them to uncheck it, simply set IsHitTestVisible equal to false. However, they may be able to tab to the control and toggle it using the space bar.
Thomas's answer works fine, but you don't even need the extra dependency property. Your button will update correctly if you have the class inherit from ToggleButton so you can override the OnToggle method, and you change the IsChecked bound property on the ViewModel.
Xaml:
<myControls:OneWayFromSourceToTargetToggle x:Name="MyCustomToggleButton"
Command="{Binding Path=ToggleDoStuffCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"
IsChecked="{Binding Path=ToggleIsCheckedConditionVar,
Mode=OneWay}"
/>
Added ToggleButton Class:
public class OneWayFromSourceToTargetToggle : ToggleButton
{
/// <summary>
/// Overrides the OnToggle method, so it does not set the IsChecked Property automatically
/// </summary>
protected override void OnToggle()
{
// do nothing
}
}
Then in the ViewModel just set bool ToggleIsCheckedCondition to true or false. This is a nice way to do it because you are following good MVVM practices.
ViewModel:
public bool ToggleIsCheckedCondition
{
get { return _toggleIsCheckedCondition; }
set
{
if (_toggleIsCheckedCondition != value)
{
_toggleIsCheckedCondition = value;
NotifyPropertyChanged("ToggleIsCheckedCondition");
}
}
}
public ICommand ToggleDoStuffCommand
{
get {
return _toggleDoStuffCommand ??
(_toggleDoStuffCommand = new RelayCommand(ExecuteToggleDoStuffCommand));
}
}
private void ExecuteToggleDoStuffCommand(object param)
{
var btn = param as ToggleButton;
if (btn?.IsChecked == null)
{
return;
}
// has not been updated yet at this point
ToggleIsCheckedCondition = btn.IsChecked == false;
// do stuff
}
}
Adding a little bit to #Joachim-Mairböck's great answer in case you want to do the same programmatically:
new RadioButton {
...
GroupName = "myButtonGroup"
Style = Application.Current.TryFindResource(typeof(ToggleButton)) as Style
...
}
I have an INotifyProperty Screen item that I have bound to a wpf control.
Ok... I Simplified everything and am posting more code. I have a MainViewModel with the selected screen property.
public Screen SelectedScreen
{
get { return this.selectedScreen; }
set
{
this.selectedScreen = value;
this.OnPropertyChanged("SelectedScreen");
}
}
I have a textbox that is bound to this property:
<TextBlock Text="{Binding Path=SelectedScreen.ScreenNumber}" />
This all works initially. I have created another control that is changing the selected screen with the following code.
public Screen SelectedScreen
{
get { return (Screen)GetValue(SelectedScreenProperty); }
set
{
this.SetValue(SelectedScreenProperty, value);
for (int x = 0; x < this.Screens.Count; ++x)
this.Screens[x].IsSelected = false;
value.IsSelected = true;
}
}
public ObservableCollection<Screen> Screens
{
get { return (ObservableCollection<Screen>)GetValue(ScreensProperty); }
set { this.SetValue(ScreensProperty, value); }
}
public static readonly DependencyProperty SelectedScreenProperty =
DependencyProperty.Register("SelectedScreen",
typeof(Screen),
typeof(ScreenSelection));
public static readonly DependencyProperty ScreensProperty =
DependencyProperty.Register("Screens",
typeof(ObservableCollection<Screen>),
typeof(ScreenSelection),
new UIPropertyMetadata(new ObservableCollection<Screen>()));
This screen selection control is working. When I change screens and put a breakpoint on the set property of SelectedScreen it is called which then calls the SelectedScreen property of the MainViewModel. So the event is firing, but the textbox isn't updated even though it binds correctly the first time.
Does the class which contains the SelectedScreen property implement INotifyPropertyChanged? When the SelectedScreen property changes, the containing class should raise the PropertyChanged event, and typically, WPF should update the Binding.
Thank you gehho for looking at this. I figured it out and there is no way you had enough information to be able too. I was inheriting from ViewModelBase in the MainViewModel that was inheriting from ObservableObject where I implemented INotifyPropertyChanged. The problem is that I implemented the methods for INotifyPropertyChanged in both classes and WPF was listening to the wrong one. Very obscure. Very annoying. Very lasjkdf;ashdoh