I have a window called SettingsWindow and I have some user controls that can be content of the window. I have a ContentControl and I have a method in view-model that returns new instance of user control to ContentControl's content. I need to set binding properties of user control to view-model programatically.
<Window x:Class="KnitterNotebook.Views.Windows.SettingsWindow"
<Window.Resources>
<viewModels:SettingsViewModel x:Key="SettingsViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource SettingsViewModel}">
<ContentControl Content="{Binding WindowContent, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
public partial class UserSettingsUserControl : UserControl
{
public UserSettingsUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NewNicknameProperty =
DependencyProperty.Register(nameof(NewNickname), typeof(string), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string NewNickname
{
get { return GetValue(NewNicknameProperty).ToString()!; }
set { SetValue(NewNicknameProperty, value); }
}
public static readonly DependencyProperty ChangeNicknameCommandAsyncProperty =
DependencyProperty.Register(nameof(ChangeNicknameCommandAsync), typeof(ICommand), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public ICommand ChangeNicknameCommandAsync
{
get { return (GetValue(ChangeNicknameCommandAsyncProperty) as ICommand)!; }
set { SetValue(ChangeNicknameCommandAsyncProperty, value); }
}
}
public class SettingsViewModel : BaseViewModel
{
public SettingsViewModel()
{
WindowContent = new UserSettingsUserControl();
ChooseSettingsWindowContentCommand = new RelayCommand<Type>(ChooseSettingsWindowContent!);
ChangeNicknameCommandAsync = new AsyncRelayCommand(ChangeNicknameAsync);
}
private string newNickname;
public string NewNickname
{
get { return newNickname; }
set { newNickname = value; OnPropertyChanged(); }
}
public ICommand ChooseSettingsWindowContentCommand { get; private set; }
public ICommand ChangeNicknameCommandAsync { get; set; }
private void ChooseSettingsWindowContent(Type userControl)
{
if (userControl == typeof(UserSettingsUserControl))
{
WindowContent = new UserSettingsUserControl()
{
NewNickname = NewNickname,
ChangeNicknameCommandAsync = ChangeNicknameCommandAsync
};
}
}
Please take a look at private void ChooseSettingsWindowContent(Type userControl). When I use Nickname = Nickname etc., the element is not binded to view-model. I need to set binding programatically. I can't create a new instance of user control in window, because I want to return user control from the method. I read about Binding class and BindingOperations but I still can't solve how to implement it. How can I set bindings programatically in ChooseSettingsWindowContent?
Related
I have to bind Grid Drop Event and PreviewMouseLeftButtonDown event in ViewModel. I have a RelayCommand. But it is done only for passing the object, I have to pass the routed event by using the command and also for MouseButtonEventArgs. my sample code is as below, please give any suggestion for using the routed event args and MouseButtonEventArgs in viewmodel.
<Grid
x:Name="mainGrid"
AllowDrop="True"
Background="#F0F0F0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventCommandExecuter Command="{Binding GridDrop}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
<Grid Background="LightBlue" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown">
EventCommandExecuter
public class EventCommandExecuter : TriggerAction<DependencyObject>
{
#region Constructors
public EventCommandExecuter()
: this(CultureInfo.CurrentCulture)
{
}
public EventCommandExecuter(CultureInfo culture)
{
Culture = culture;
}
#endregion
#region Properties
#region Command
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null));
#endregion
#region EventArgsConverterParameter
public object EventArgsConverterParameter
{
get { return (object)GetValue(EventArgsConverterParameterProperty); }
set { SetValue(EventArgsConverterParameterProperty, value); }
}
public static readonly DependencyProperty EventArgsConverterParameterProperty =
DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null));
#endregion
public IValueConverter EventArgsConverter { get; set; }
public CultureInfo Culture { get; set; }
#endregion
protected override void Invoke(object parameter)
{
var cmd = Command;
if (cmd != null)
{
var param = parameter;
if (EventArgsConverter != null)
{
param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture);
}
if (cmd.CanExecute(param))
{
cmd.Execute(param);
}
}
}
}
I want to pass object and RoutedEventArgs like below in viewmodel. Please help
public void Grid_Drop(object sender, RoutedEventArgs e)
{
}
I feel like commands are often overkill for such simple tasks.
You can simply declare your ViewModel in the code behind of your view like so:
public partial class MainWindow : Window
{
private ViewModel _vm;
public ViewModel Vm
{
get { return _vm;}
set
{
_vm = value ;
}
}
//....Constructor here....
}
Then create a public event :
public event RoutedEventHandler OnGridDrop;
and call it in :
public void Grid_Drop(object sender, RoutedEventArgs e)
{
OnGridDrop?.Invoke(sender,e)
}
Now you only need to initialize your ViewModel:
public MainWindow()
{
InitializeComponent();
Vm = new ViewModel();
OnGridDrop += Vm.OnGridDrop;
}
and subscribe a corrsponding handler that you declared in your ViewModel.
I have a ParentViewModel which contains a ReactiveList of ChildViewModels. I would like to bind it to a ListView which will display ChildViews. ChildView binds some text to a Label and a sets an Image resource based on a status enum:
ParentViewModel.cs Simple container for ChildViewModels
public class ParentViewModel : ReactiveObject
{
public ReactiveList<ChildViewModel> Children { get; }
= new ReactiveList<ChildViewModel>();
}
ChildViewModel.cs Has some text and a Status
public class ChildViewModel : ReactiveObject
{
public string SomeText { get; set; }
public enum Status
{
Unknown,
Known
}
public Status CurrentStatus { get; set; } = Status.Unknown;
}
ParentView.xaml UserControl which wraps a ListView, using ChildView for the data template. I made sure to add the ViewModel="{Binding}" on my DataTemplate.
<UserControl x:Class="MyProject.UI.ParentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:MyProject.View_Models"
xmlns:ui="clr-namespace:MyProject.UI">
<ListView x:Name="ChildList" BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<!--I have also tried the following line, didn't work -->
<!--<DataTemplate DataType="{x:Type vm:ChildViewModel}">-->
<ui:ChildView ViewModel="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
ParentView.xaml.cs Creates a dependency property for its ParentViewModel, and binds the view model's Children to the ChildList ListView
public partial class ParentView : UserControl, IViewFor<ParentViewModel>
{
public static readonly DependencyProperty ViewModelProperty
= DependencyProperty.Register(
"ViewModel",
typeof(ParentViewModel),
typeof(ParentView));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ParentViewModel)value; }
}
public ParentViewModel ViewModel
{
get { return (ParentViewModel )GetValue(ViewModelProperty); }
set
{
SetValue(ViewModelProperty, value);
BindToViewModel(value);
}
}
private void BindToViewModel(ParentViewModel viewModel)
{
this.OneWayBind(viewModel, vm => vm.Children, v => v.ChildList.ItemsSource);
}
}
ChildView.xaml Simple UserControl with an Image and a Label
<UserControl x:Class="MyProject.UI.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Image Name="StatusIcon" Margin="2" />
<Label Name="DisplayName" />
</StackPanel>
</UserControl>
ChildView.xaml.cs Binds the ChildViewModel and sets image data
public partial class ChildView : UserControl, IViewFor<ChildViewModel>
{
public static readonly DependencyProperty ViewModelProperty
= DependencyProperty.Register(
"ViewModel",
typeof(ChildViewModel),
typeof(ChildView));
public ChildView()
{
InitializeComponent();
}
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ChildViewModel)value; }
}
public ChildViewModel ViewModel
{
get { return (ChildViewModel )GetValue(ViewModelProperty); }
set
{
SetValue(ViewModelProperty, value);
BindToViewModel(value);
}
}
private void BindToViewModel(ChildViewModel viewModel)
{
viewModel
.WhenAnyValue(vm => vm.CurrentStatus)
.Subscribe(status =>
{
// set StatusIcon's image based on status
});
this.Bind(viewModel, vm => vm.DisplayName, v => v.SomeText);
}
}
I have set breakpoints in ChildView to see if the ViewModel properties hit, but they never do. When I add ChildViewModels to ParentVieWModel.Children, a ChildView is created, but it is never bound properly. I could subscribe to Children's CollectionChanged event and manually set the binding, but I would like to do this the proper XAML/ReactiveUI way. What am I missing?
I think you need to fully set up your properties in your ChildViewModel so that they raise changes in the ViewModel that your ChildView can subscribe to. See ViewModels documentation.
string someText ;
public string SomeText {
get { return someText; }
set { this.RaiseAndSetIfChanged(ref someText , value); }
}
Status currentStatus;
public Status CurrentStatus {
get { return currentStatus; }
set { this.RaiseAndSetIfChanged(ref currentStatus, value); }
}
Or if one or both are readonly.
readonly ObservableAsPropertyHelper<string> someText;
public string SomeText{
get { return someText.Value; }
}
readonly ObservableAsPropertyHelper<Status> currentStatus;
public Status CurrentStatus{
get { return currentStatus.Value; }
}
I have a simple Window with a TextBox
XAML
<Window x:Class="Configurator.ConfiguratorWindow"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</Window>
in the code behind
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata());
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set {
SetValue(DescriptionProperty, value);
_actual_monitor.Description = value;
}
}
}
the graphic is updating right, but when i change the text in the textbox and lose focus it doesn't update the source property.
What is wrong?
DependencyProperties are used for UserControls rather than ViewModel type bindings.
You should
Create a ConfigurationWindowViewModel (Read about MVVM) and implement INotifyPropertyChanged
Create a Property Description that utilizes the INotifyPropertyChanged
Create a new instance of that view model to be set to the DataContext of your ConfigurationWindow.
The getter and setter of the CLR wrapper of a dependency property must not contain any other code than GetValue and SetValue. The reason is explained in the XAML Loading and Dependency Properties article on MSDN.
So remove the _actual_monitor.Description = value; assignment from the setter and add a PropertyChangedCallback to react on property value changes:
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(ConfiguratorWindow),
new PropertyMetadata(DescriptionPropertyChanged));
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
private static void DescriptionPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ConfiguratorWindow obj = d as ConfiguratorWindow;
obj._actual_monitor.Text = (string)e.newValue;
}
}
Try this
<Window x:Class="Configurator.ConfiguratorWindow"
xmlns:myWindow="clr-namespace:YourNamespace"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type myWindow}}, Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata(null, CallBack);
private static void callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var foo = d as ConfiguratorWindow ;
all you need to do, you can do here
}
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set { SetValue(DescriptionProperty, value);}
}
}
But it would be much easier to just have a View Model and bind to property there.
My MainWindow uses the INotifyPropertyChanged interface. I'm using the OnPropertyChanged function that I've been using for a while, which works.
In my MainWindow code-behind I have this:
public ObservableCollection<bool> MwOc { get; set; }
private bool _mwBool;
public bool MwBool { get { return _mwBool; } set { _mwBool = value; OnPropertyChanged(); } }
public MainWindow()
{
InitializeComponent();
MwOc = new ObservableCollection<bool>();
MwOc.Add(false);
MwBool = true;
Console.WriteLine("MwOc: " + MwOc.Count);
Console.WriteLine("MwBool: " + MwBool);
DataContext = this;
}
All my MainWindow xaml does is this:
<local:UserControl1 x:Name="Control" UcOc="{Binding MwOc}" UcBool="{Binding MwBool}" />
My UserControl has two dependency properties: UcOc an ObservableCollection<bool> and UcBool a bool
Here is my UserControl code:
public ObservableCollection<bool> UcOc
{
get { return (ObservableCollection<bool>)GetValue(UcOcProperty); }
set { SetValue(UcOcProperty, value); }
}
public static readonly DependencyProperty UcOcProperty =
DependencyProperty.Register("UcOc", typeof(ObservableCollection<bool>), typeof(UserControl1));
public bool UcBool
{
get { return (bool)GetValue(UcBoolProperty); }
set { SetValue(UcBoolProperty, value); }
}
public static readonly DependencyProperty UcBoolProperty =
DependencyProperty.Register("UcBool", typeof(bool), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
UcOc = UcOc ?? new ObservableCollection<bool>();
DataContextChanged += (o, e) => { Console.WriteLine("DataContextChanged"); Print(); };
}
public void Print()
{
UcOc = UcOc ?? new ObservableCollection<bool>();
Console.WriteLine("UcOc: " + UcOc.Count);
Console.WriteLine("UcBool: " + UcBool);
}
My UserControl xaml is empty (just has the default <Grid></Grid>)
The output of this program is
MwOc: 1
MwBool: True
DataContextChanged
UcOc: 0
UcBool: False
How should I update the UserControl properties when its DataContext changes?
In the MainWindow xaml, the bindings need the NotifyOnTargetUpdated property set to true.
Instead of:
<local:UserControl1 x:Name="Control" UcOc="{Binding MwOc}" UcBool="{Binding MwBool}" />
use:
<local:UserControl1 x:Name="Control" UcOc="{Binding MwOc, NotifyOnTargetUpdated=true}" UcBool="{Binding MwBool, NotifyOnTargetUpdated=true}" />
In the UserControl, subscribing to the DataContextChanged event also causes the binding to fail on the ObservableCollection<bool> but not the bool. For currently unknown reasons.
I want to create master page in mvvm. I created a viewbox that it's name is container for showing my usercontrols and I have two classes, RelayCommand and ViewModel.
Here is my code:
public class ViewModel
{
MainWindow objMainWindow = new MainWindow();
UserControls.History objHistory = new UserControls.History();
UserControls.NewItem objNewItem = new UserControls.NewItem();
UserControls.SideEffect objSideEffect = new UserControls.SideEffect();
public ViewModel()
{
OpenCommand = new RelayCommand(Open);
}
private ICommand openCommand;
public ICommand OpenCommand
{
get { return openCommand; }
set { openCommand = value; }
}
public void Open(object sender)
{
if (sender.ToString() == "btnHistory")
{
objMainWindow.Container.Child = objHistory;
}
if (sender.ToString() == "btnNewItem")
{
}
if (sender.ToString() == "btnSideEffect")
{
}
}
}
And this is my RelayCommand:
public class RelayCommand:ICommand
{
public RelayCommand(Action<object> _action)
{
actionCommand = _action;
}
private Action<object> actionCommand;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (parameter !=null)
{
actionCommand(parameter);
}
else
{
actionCommand("Null");
}
}
}
but when I run solution I faced with NullRefrenceException when it wanted to show my child of container.
I don't know how to make this work.
Your MainWindow instantiates when your program starts. So you shouldn't instantiate it again in your ViewModel (i.e. this line: MainWindow objMainWindow = new MainWindow();). You should use DataBinding instead.
Here is a sample code that gives you an idea:
First define a property of type FrameworkElement in you ViewModel and set it's value to your desired UserControl in the Open method.
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
FrameworkElement _myUc;
public FrameworkElement MyUserControl
{
get
{
return _myUc;
}
set
{
_myUc= value;
OnPropertyChanged("MyUserControl");
}
}
public ViewModel()
{
OpenCommand = new RelayCommand(Open);
}
public void Open(object sender)
{
if (sender.ToString() == "btnHistory")
{
MyUserControl = objHistory;
}
}
// rest of your view model ...
}
Then instantiate your ViewModel as the DataContext of your MainWindow in the Constructor.
MainWindow:
public ViewModel MyViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
MyViewModel = new ViewModel();
DataContext = MyViewModel;
}
And Finally use a ContentControl (instead of ViewBox) [see my note] and bind it's Content to the MyUserControl property of your ViewModel.
XAML:
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding MyUserControl}" x:Name="Container"/>
<Button Grid.Row="1" Name="btnHistory" Content="ShowHistory" Command="{Binding OpenCommand}" />
</Grid>
This way each time MyUserControl changes, the ContentControl shows your desired UserControl.
Note that Child property of ViewBox is not a DependencyProperty and thus not bind-able.