What I want to do is to bind my usercontrol's label to a property value in ViewModel.
But I also want to get notified when the label changed ( and to do other work like extract the label's new value to modify grid width and so on).
How to do this?
what I did is:
Have a viewmodel with an Voltage property, which is what I want to display.
UnitVm.cs
private int m_V;
public int VoltInVm
{
get
{ return m_V; }
set
{
if (m_V != value)
{
Set<int>(ref m_V, value, nameof(Volt));
}
}
}
and my usercontrol: Unit.cs
public partial class Unit : UserControl
{
public static readonly DependencyProperty VoltProperty =
DependencyProperty.Register("Volt", typeof(int), typeof(Unit),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender, (o, e) => ((Unit)o).OnVoltChanged(o, e)));
private void OnVoltChanged(double dVolt)
{
double dWidth;
if (double.TryParse(strVal, out dWidth))
{
dWidth = dVolt / 380 * 100;
if (dWidth > 100)
dWidth = 100;
gridVolt.ColumnDefinitions[0].Width = new GridLength(dWidth, GridUnitType.Star);
gridVolt.ColumnDefinitions[1].Width = new GridLength(100 - dWidth, GridUnitType.Star);
}
}
public int Volt
{
get { return (int)GetValue(VoltProperty); }
set
{
SetValue(VoltProperty, value);
}
}
the DependencyProperty of VoltProperty is defined, and the work I want to do is written inside OnVoltChanged.
I mean when accepting change from ViewModel, I can call OnVoltChanged.
To use the usercontrol of Unit in a main window:
<DockPanel DataContext="{Binding UnitVm, Source={StaticResource Locator}}">
<Viewbox
<Label x:Name="lblVolt" Content="{Binding VoltInVm}" />
</Viewbox>
</DockPanel>
lblVolt binding to UnitVm context can update with new voltage values correctly.
But how to bind to DependencyProperty of Volt in Unit?
And is this the right way?
Thanks in advance.
Ting
The view model property setter is not implemented correctly.
It should look like shown below, i.e. use the correct property name nameof(VoltInVm) and probably not check m_V != value before calling Set, because that is already done by the Set method.
private int voltInVm;
public int VoltInVm
{
get { return voltInVm; }
set { Set<int>(ref voltInVm, value, nameof(VoltInVm)); }
}
When you now bind the UserControl's property like
<local:Unit Volt="{Binding VoltInVm}"/>
the PropertyChangedCallback of the Volt dependency property will be called each time the VoltInVm property changes its value.
It is however unclear what (o, e) => ((Unit)o).OnVoltChanged(o, e) in the dependency property registration is supposed to be. It should certainly look like this:
(o, e) => ((Unit)o).OnVoltChanged((int)e.NewValue)
and the method should be declared with an int argument instead of double:
private void OnVoltChanged(int volt) ...
Or - certainly better - change all voltage property types to double, in the control and in the view model.
Related
I have a custom user control named CharacteristicSlider with CharValue property that displays the current characteristic value.
public partial class CharacteristicSlider : UserControl
{
.....
public int CharValue
{
get => (int)GetValue(CharValueProperty);
set => SetValue(CharValueProperty, value);
}
public static readonly DependencyProperty CharValueProperty =
DependencyProperty.Register("CharValue", typeof(int),
typeof(CharacteristicSlider),
new PropertyMetadata(0, OnCharValuePropertyChanged));
private static void OnCharValuePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as CharacteristicSlider;
if (control is null) return;
control.CharValue = (int)e.NewValue;
}
......
}
I use this control in my MainWindow like this
<components:CharacteristicSlider components:CharValue="{Binding Strength}"
x:Name="StrengthSlider" components:CharName="Strength"
Margin="40,20,40,20" Command="{Binding UpdateCharValue}" />
The strength property is just a wrap around external class library Character class. OnPropertyChanged invokes the PropertyChanged event of INotityPropertyChanged. I checked with debugger and the property names passes correctly, it is "Strength"
public int Strength
{
get => _character.Strength;
set
{
_character.Strength = value;
OnPropertyChanged();
}
}
The problem is that components:CharValue="{Binding Strength}" part does not work and does not update the CharValue at all.
But if I bind MainWindow's title to Strength property (Title="{Binding Strength}") the title of the window is updated, while the slider's stays the same. Why the slider's DependencyProperty is never updated though I have a binding?
Simple question. Having:
<ScrollBar ... />
How can I detect when Maximum is changed? E.g. for Value there is an event.
Typically there would be a binding of some kind. I was thinking maybe it is possible to get this binding, create dependency property and bind to it instead, then I can register a callback when this new dependency property is changed... but that sounds complicated nor I am sure it is acceptable solution to all cases (e.g. what if another binding is set, how can I detect this kind of change). Polling?
You can create a custom class such as:
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
// do stuff
base.OnMaximumChanged(oldMaximum, newMaximum);
}
}
Or
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == System.Windows.Controls.Primitives.ScrollBar.MaximumProperty)
{
// do stuff
}
base.OnPropertyChanged(e);
}
}
It is important to understand what any property can be a source for multiple bindings. We can create a new target (new dependency property) which is then perfectly able to report about any change done to a property:
Create a new dependency property with callback.
Bind it to any other property to monitor for changes.
public partial class MainWindow : Window
{
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(MainWindow), new PropertyMetadata(0, (d, e) =>
{
// value has changed
}));
public MainWindow()
{
InitializeComponent();
var scrollBar = ... // instance of scrollbar
BindingOperations.SetBinding(this, MaximumProperty,
new Binding(nameof(RangeBase.Maximum)) { Source = scrollBar });
}
}
This question has been asked once before however it did not get a satisfactory answer...
I am following the MVVM design archetype and I would like to be able to change the width and height of the window. To do this I decided to create two properties in my ViewModel:
private int xWidth;
public int XWidth
{
get { return xWidth; }
set
{
xWidth = value;
RaisePropertyChanged("XWidth");
}
}
private int yHeight;
public int YHeight
{
get { return yHeight; }
set
{
yHeight = value;
RaisePropertyChanged("YHeight");
}
}
I then bound the height and width to those properties:
Height="{Binding YHeight}" Width="{Binding XWidth}">
Finally I created an method which changes those values:
private void HomeExecute()
{
ShowMain = true;
ShowSearch = false;
YHeight = 350;
XWidth = 525;
}
This however is not working. When the method executes the window doesn't change size.
I know that the View is bound correctly to the ViewModel as other bindings work.
I also know that the method is being run as the ShowMain property gets changed.
I had a hunch that it might need a converter of sorts as I am passing to the width and height properties an int however my research didn't lead to anything.
I'm not sure, why the binding does not work, maybe it has something to do with the fact, that window is not just regular control, but some kind of wrapper around WinAPI. However, you can still use good old code behind event-based approach, even without breaking MVVM separation of concerns.
I have written MVVM sample, that does not use xaml binding, but implements two-way binding using "plain eventhandlers":
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
Loaded += delegate
{
Height = ViewModel.YHeight;
Width = ViewModel.XWidth;
ViewModel.PropertyChanged += ViewModelOnPropertyChanged;
SizeChanged += MainWindow_SizeChanged;
};
Unloaded += delegate
{
ViewModel.PropertyChanged -= ViewModelOnPropertyChanged;
SizeChanged -= MainWindow_SizeChanged;
};
}
public MainWindowViewModel ViewModel
{
get { return (MainWindowViewModel)DataContext; }
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "YHeight")
{
Height = ViewModel.YHeight;
}
if (e.PropertyName == "XWidth")
{
Width = ViewModel.XWidth;
}
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.XWidth = e.NewSize.Width;
ViewModel.YHeight = e.NewSize.Height;
}
}
If you need to reuse this behaviour, you can move all the logic to behavior implemented as attached property or custom blend behavior
I recommend you to raise PropertyChanged only when property value changed. e.g if (xWidth != value) OnPropertyChanged("XWidth")
You could just add the binding mode "two way" to your height and width bindings. Just edit your XAML to look like this:
Height="{Binding YHeight, Mode=TwoWay}" Width="{Binding XWidth, Mode=TwoWay}">
That's it. You can now set the window size from your viewmodel.
I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!
I am using the Model-View-ViewModel architecture in a WPF application I am building, and I would like a specific ViewModel to actually be reactive to the size of the view (not a normal use-case of the MVVM approach, I know).
Essentially, I have a ScrollViewer object and I want the viewmodel to observe the width and height of the scrollviewer and then be able to do things accordingly depending on what that width and height are.
I'd like to do something like this:
<ScrollViewer ViewportWidth="{Binding Path=MyViewportWidth, Mode=OneWayToSource}" ViewportHeight="{Binding Path=MyViewportHeight, Mode=OneWayToSource}" />
But of course this is impossible to do because "ViewportWidth" and "ViewportHeight" cannot be "bound to" (a.k.a. act as binding targets) because they are read-only dependency properties (even though I am not writing to them at all in this binding since it is OneWayToSource).
Anyone know of a good method to be able to do something like this?
You could try running something OnLoaded or OnResizeChanged that updates the viewmodel
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
ViewModel vm = sv.DataContext as ViewModel;
vm.ScrollViewerHeight = sv.ViewportHeight;
vm.ScrollViewerWidth = sv.ViewportWidth;
}
Ok, this is a really old question, but I thought I'd share for posterity, since I've solved this one myself. The best solution I've found is to create a user control that derives from the ScrollView class and implements the properties you want - which are of course linked to the non-bindable properties of the base class.
You can use the OnPropertyChanged function to monitor those properties and keep the values in sync.
Here's the full code-behind of my custom usercontrol called DynamicScrollViewer. Notice that I have four bindable dependency properties called DynamicHorizontalOffset, DynamicVerticalOffset, DynamicViewportWidth, and DynamicViewportHeight.
The two offset properties allow both read and write control of the offset, while the viewport properties are essentially read-only.
I had to use this class when creating a complex animation editor control in which various components (labels at the left, nodes in the middle, timeline at top) needed to scroll synchronously, but only in limited aspects, and were all bound to common external scrollbars. Think of locking a section of rows in spreadsheet, and you get the idea.
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class DynamicScrollViewer : ScrollViewer
{
public DynamicScrollViewer()
{
InitializeComponent();
}
public double DynamicHorizontalOffset
{
get { return (double)GetValue(DynamicHorizontalOffsetProperty); }
set { SetValue(DynamicHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicHorizontalOffsetProperty =
DependencyProperty.Register("DynamicHorizontalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicVerticalOffset
{
get { return (double)GetValue(DynamicVerticalOffsetProperty); }
set { SetValue(DynamicVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicVerticalOffsetProperty =
DependencyProperty.Register("DynamicVerticalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportWidth
{
get { return (double)GetValue(DynamicViewportWidthProperty); }
set { SetValue(DynamicViewportWidthProperty, value); }
}
public static readonly DependencyProperty DynamicViewportWidthProperty =
DependencyProperty.Register("DynamicViewportWidth", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportHeight
{
get { return (double)GetValue(DynamicViewportHeightProperty); }
set { SetValue(DynamicViewportHeightProperty, value); }
}
public static readonly DependencyProperty DynamicViewportHeightProperty =
DependencyProperty.Register("DynamicViewportHeight", typeof(double), typeof(DynamicScrollViewer));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DynamicVerticalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetVerticalOffset(DynamicVerticalOffset);
}
else if (e.Property == DynamicHorizontalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetHorizontalOffset(DynamicHorizontalOffset);
}
else if (e.Property == HorizontalOffsetProperty)
{
DynamicHorizontalOffset = (double)e.NewValue;
}
else if (e.Property == VerticalOffsetProperty)
{
DynamicVerticalOffset = (double)e.NewValue;
}
else if (e.Property == ViewportWidthProperty)
{
DynamicViewportWidth = (double)e.NewValue;
}
else if (e.Property == ViewportHeightProperty)
{
DynamicViewportHeight = (double)e.NewValue;
}
}
}
}