This website has greatly benefited me, but now I have to ask a question of my own.
I am new to C# and MVVM applications..
Now I am building an app with different views, one view is the navigation view which should show other views depending on the navigation buttons. These buttons depend on the entered value in a view.
Example:
There is a navigationView containing a ContentControl and two buttons (nextStep and previousStep). I have put several textboxes in a view (parameterView) and associated model (parameterViewModel) which is displayed in the ContentControl. When all parameters are entered, the user may, by means of a button (nextStep mentioned before), go to the next step/view (checkDataView).
Now the button (in navigationView) must therefore be visible when all parameters are filled in parameterView, and hidden when one parameter is not filled in. The nextStep button should activate another page in the ContentControl.
I can navigate with checkboxes or radio buttons, but only without dependence on values in another viewModel.
What should I do to get the dependency of parameters in another viewModel?
My NavigationView ContentControl and buttons are defined as:
<ContentControl Grid.Row="1"
Grid.ColumnSpan="5"
Content="{Binding CurrentItemView}" />
<Button Grid.Column="0"
Grid.Row="2"
Content="Next Step"
Style="{StaticResource SubMenuButton}"
Visibility="{Binding PreviousStepCommandVisibility}"
Command="{Binding PreviousStepCommand}"/>
<Button Grid.Column="3"
Grid.Row="2"
Content="Previous Step"
Style="{StaticResource SubMenuButton}"
Visibility="{Binding NextStepCommandVisibility}"
Command="{Binding NextStepCommand}"/>
My ViewModel of above View:
namespace SomeApp.MVVM.ViewModel
{
class GenerateMenuViewModel : BaseViewModel
{
public RelayCommand PreviousStepCommand { get; set; }
public RelayCommand NextStepCommand { get; set; }
private Visibility _previousStepCommandVisibility;
public Visibility PreviousStepCommandVisibility
{
get { return _previousStepCommandVisibility; }
set { _previousStepCommandVisibility = value; }
}
private Visibility _nextStepCommandVisibility;
public Visibility NextStepCommandVisibility
{
get { return _nextStepCommandVisibility; }
set { _nextStepCommandVisibility = value; }
}
public SomethingViewModel SomethingVM { get; set; }
private object _currentItemView;
public object CurrentItemView
{
get { return _currentItemView; }
set
{
_currentItemView = value;
OnPropertyChanged();
}
}
public GenerateMenuViewModel()
{
SomethingVM = new SomethingViewModel();
CurrentItemView = SomethingVM;
}
}
}
TextBoxes in View2, which values give dependence to the navigation buttons, are defined as:
<TextBox Text="{Binding Paramater1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Paramater2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Paramater3, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The ViewModel which belongs to the above View:
namespace SomeApp.MVVM.ViewModel
{
class View1ViewModel : BaseViewModel
{
public View1ViewModel()
{
}
private string _parameter1;
public string Parameter1
{
get { return _parameter1; }
set { _parameter1 = value; OnPropertyChanged(); }
}
private string _parameter2;
public string Parameter2
{
get { return _parameter2; }
set { _parameter2 = value; OnPropertyChanged(); }
}
private string _parameter3;
public string Parameter3
{
get { return _parameter3; }
set { _parameter3 = value; OnPropertyChanged(); }
}
}
}
The simple way is to add a property in viewModel to indicate that all the parameters are validated and succeeded.
class View1
{
public bool Isvalid { get => !validationResults.Values.Contains(false); }
Dictionary<string, bool> validationResults = new Dictionary<string, bool>
{
{ nameof(Val1), false }
};
string val1 = "";
public string Val1
{
get => val1; set
{
val1 = value;
OnPropertyChanged();
validationResults[nameof(Val1)] = !string.IsNullOrEmpty(value); //Validation goes here
}
}
Related
I am not sure if this is the correct question to ask but I don't know how else I could ask it.
In my project I have a LogInViewModel.cs
class LogInViewModel : BaseObservableObject
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
}
}
a MenuViewModel.cs
class MenuViewModel : BaseObservableObject
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
}
}
the Views for both LogInView.xaml and MenuView.xaml are
<StackPanel>
<TextBox Text="{Binding Text}"/>
<local:NumPad/>
</StackPanel>
NumPad.xaml is a UserControl that has 2 buttons.
What I want is when I click one of the buttons in LogInView I want to set the text of LogInViewModel to some string and when I click one of the buttons in MenuView I want to set the text of MenuViewModel to some string. I want to create a UserControl keyboard view with multiple buttons and be able to use it in multiple views but have it add characters(string) to the TextBox in the View they are located, I could create ICommands for every button in my ViewModels like so
class LogInViewModel : BaseObservableObject
{
public ICommand SetTextCommand1 { get; set; }
public ICommand SetTextCommand2 { get; set; }
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
SetTextCommand1 = new BaseICommand(SetText1);
SetTextCommand2 = new BaseICommand(SetText2);
}
private void SetText1(object obj)
{
Text = "1";
}
private void SetText2(object obj)
{
Text = "2";
}
}
and NumPad.xaml would be
<StackPanel>
<Button Command="{Binding SetTextCommand1}"/>
<Button Command="{Binding SetTextCommand2}"/>
</StackPanel>
and add the same ICommands to MenuViewModel, but it does not seem right because I want my NumPad.xaml to be a keyboard eventually with lots of buttons.
I think that if you would like to stick with commands, using single command + control it with parameter is better option, e.g.:
<StackPanel>
<Button Command="{Binding SetTextCommand}" CommandParameter="1" />
<Button Command="{Binding SetTextCommand}" CommandParameter="2" />
</StackPanel>
with
class LogInViewModel : BaseObservableObject
{
public ICommand SetTextCommand { get; set; }
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
SetTextCommand1 = new BaseICommand(SetText);
}
private void SetText(object obj)
{
Text = obj?.ToString();
}
}
Anyway, I would advice to refactor the solution to something cleaner, meaning e.g.:
your NumPad control shall define public event or command (like KeyPressed)
the control that embeds NumPad control (LogInView & MenuView in your case) shall explicitly use/bind the event or command of the NumPad
buttons in the NumPad xaml should use NumPad control's API only; specifying data binding in the reusable control in the way you did is similar to using "magic strings" (e.g. you have to make sure that each ViewModel must define command with the "SetTextCommand" name, it's simply error prone)
So that at the end, you use NumPad similar to:
<StackPanel>
<TextBox Text="{Binding Text}"/>
<local:NumPad KeyPressed="{Binding SetTextCommand}"/>
</StackPanel>
In my application i have the following MasterViewModel1-class.
public class MasterViewModel1 : ViewModelBase
{
private ObservableCollection<ObservableObject> _MainGrid;
public ObservableCollection<ObservableObject> MainGrid
{
get => _MainGrid;
set
{
_MainGrid = value;
RaisePropertyChanged();
}
}
public ObservableCollection<FilterItem> FilterItems
{
get;
set;
}
public MasterViewModel1()
{
CreateDefaultMenu();
}
public void CreateDefaultMenu()
{
FilterItems = new ObservableCollection<FilterItem>
{
new FilterItem(OnFilterClicked)
{
Content = "Filter"
},
new FilterItem(OnFilterCancelClicked)
{
Content = "Filter aufheben"
}
};
}
public virtual void OnFilterClicked() { }
public virtual void OnFilterCancelClicked() { }
The MasterViewModel1-class is inherited by the TestViewModel-class.
public class TestViewModel : MasterViewModel1
{
private Kunde _NeuerKunde;
public Kunde NeuerKunde
{
get => _NeuerKunde;
set => _NeuerKunde = value;
}
private string _Kundenmatchcode;
public string Kundenmatchcode
{
get => _Kundenmatchcode;
set
{
_Kundenmatchcode = value;
RaisePropertyChanged();
}
}
public TestViewModel()
{
NeuerKunde = new Kunde();
}
}
I use the MasterViewModel1-class and its view for reusable reasons, because in the future there will be many more views which will inherit the MasterViewModel.
Inside the MasterView in need to bind to both, the MasterViewModel, so i have the "Base-Design".
And i need to bind to the "Sub"ViewModel, in this example the TestViewModel.
View of the MasterViewModel1
In the image u can see the MasterView. The red marked region is the place where the TestViewModel (TestView) should be placed. I can't use staticresource!!! It have to be dynamic, so if i instanciate another ViewModel, which also inherites from MasterViewModel1. The red marked region should change depending on the instantiated ViewModel.
I hope it's clear enought.
If u need further informations please ask.
Generally, all public properties of a superclass are visible and accessible via every subclass. You can bind to every public property.
If you want to change the layout or appearance of a view based on the actual implementation or type, you should use a DataTemplate which describes how the view is structured and bound to the model's data.
A simple ContentControl will serve as the dynamic view host.
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get => this.currentView;
set
{
this.currentView= value;
OnPropertyChanged();
}
}
public ICommand ToggleViewCommand => new RelayCommand(param => this.CurrentView = this.Views.FirstOrDefault(view => view != this.CurrentView));
private List<ViewModelBase> Views { get; }
public MainViewModel()
{
this.Views = new ObservableCollection<ViewModelBase>()
{
new TestViewModel() { Value = "TestViewModel View" },
new AnotherTestViewModel() { Name = "AnotherTestViewModel View" }
}
this.CurrentView = this.Views.First();
}
}
TestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string value;
public string Value
{
get => this.value;
set
{
this.value = value;
OnPropertyChanged();
}
}
}
AnotherTestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
}
TestView.xaml
<Window>
<Window.DataContext>
<TestViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Value}" />
</Window>
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as an implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type TestViewModel}">
<!-- Show a view as a UserControl -->
<TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type AnotherTestViewModel}">
<!-- Or add a elements -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Rectangle Height="80" Width="80" Fill="Red" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Command="{Binding ToggleViewCommand}" Content="Toggle View" />
<!--
Host of the different views based on the actual model type (dynamic view).
The implicit DataTemplates will apply automatically
and show the view that maps to the current CurrentView view model type
-->
<ContentControl Content="{Binding CurrentView}" />
</StackPanel>
</Window>
Here is a class with undefined variable that needs to be passed into the WPF window.
public class SelectedVal<T>
{
public T val {get;set;}
}
Window:
public partial class SOMEDialogue : Window
{
public List<SelectedVal<T>> returnlist { get { return FullList; } }
public List<SelectedVal<T>> FullList = new List<SelectedVal<T>>();
public SOMEDialogue (List<SelectedVal<T>> inputVal)
{
InitializeComponent();
}
}
So here is the question, how can I do this properly to get the T and have a global variable set in my WPF?
Edited (code edited too):
The purpose for the WPF is:
A list of SelectedVal<T> input
Display this input in this WPF
Depend on the T type, user can do something about this input
When finished a return List<SelectedVal<T>> returnlist can be
accessed
This is the basic idea I'm describing. Let me know if you hit any snags. I'm guessing that the search text and the min/max int values are properties of the dialog as a whole. I'm also assuming that there may be a mixture of item types in the collection, which may be an assumption too far. Can you clarify that?
Selected value classes
public interface ISelectedVal
{
Object Val { get; set; }
}
public class SelectedVal<T> : ISelectedVal
{
public T Val { get; set; }
object ISelectedVal.Val
{
get => this.Val;
set => this.Val = (T)value;
}
}
public class StringVal : SelectedVal<String>
{
}
public class IntVal : SelectedVal<int>
{
}
Dialog Viewmodel
public class SomeDialogViewModel : ViewModelBase
{
public SomeDialogViewModel(List<ISelectedVal> values)
{
FullList = values;
}
public List<ISelectedVal> FullList { get; set; }
private String _searchText = default(String);
public String SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged();
}
}
}
private int _minInt = default(int);
public int MinInt
{
get { return _minInt; }
set
{
if (value != _minInt)
{
_minInt = value;
OnPropertyChanged();
}
}
}
private int _maxInt = default(int);
public int MaxInt
{
get { return _maxInt; }
set
{
if (value != _maxInt)
{
_maxInt = value;
OnPropertyChanged();
}
}
}
}
.xaml.cs
public SOMEDialogue (List<ISelectedVal> inputValues)
{
InitializeComponent();
DataContext = new SomeDialogViewModel(inputValues);
}
XAML
<Window.Resources>
<DataTemplate DataType="{x:Type local:StringVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Search text:</Label>
<TextBox Text="{Binding DataContext.SearchText, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:IntVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Min value:</Label>
<TextBox Text="{Binding DataContext.MinIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<Label>Max value:</Label>
<TextBox Text="{Binding DataContext.MaxIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding FullList}"
/>
</Grid>
I try to binding from observerCollection to ToggleSwitch(MahApp)
but it isnt work
XAML:
<mah:ToggleSwitch Name="switchLEDA" IsChecked="{Binding ConfParams[0], Mode=TwoWay}" Content="" Grid.Column="2" Grid.Row="2"/>
ViewModel:
public ObservableCollection<bool> ConfParams
{
get { return _chromaConfigurationModel.ConfParams; }
set { _chromaConfigurationModel.ConfParams = value; }
}
Model:
private ObservableCollection<bool> _confParams;
public ObservableCollection<bool> ConfParams
{
get { return _confParams; }
set { _confParams = value; }
}
any one can halp me pls?
You can't change the value of a bool in an ObservableCollection<bool> so your TwoWay binding won't work. bool is a value type that is copied when it is passed around.
You should bind to a property that can actually be set:
public class ConfParams
{
public bool Value { get; set; }
}
View Model:
public ObservableCollection<ConfParams> ConfParams
{
get { return _chromaConfigurationModel.ConfParams; }
set { _chromaConfigurationModel.ConfParams = value; }
}
View:
<mah:ToggleSwitch Name="switchLEDA" IsChecked="{Binding ConfParams[0].Value, Mode=TwoWay}" Content="" Grid.Column="2" Grid.Row="2"/>
Here is a simple screen with one textblock which is "" initially, a button called "Set Text" which sets the text to the textblock and another button called "Clear text" which always clears the text in the textblock. This is how the XAML looks like.
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}"
IsEnabled="{Binding CanCancel, Mode=TwoWay}"/>
</StackPanel>
Here is my ViewModel code.
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(HandleSetText, CanExecute);
CancelCommand = new RelayCommand(HandleClearButtonClick, CanExecuteCancel);
}
private void HandleSetText(string number)
{
DisplayText = number;
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
}
}
private bool _canCancel;
public bool CanCancel
{
get
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
set
{
_canCancel = value;
RaisePropertyChanged("CanCancel");
}
}
private bool CanExecute()
{
return true;
}
private bool CanExecuteCancel()
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
private void HandleClearButtonClick()
{
DisplayText = "";
}
private void HandleSetText()
{
DisplayText = "Hello";
}
}
The problem : When the page is loaded, the "Clear text" button is disabled which is expected and works fine as intended.
When i click on "Set Text", i set a text to a textblock by setting a text value to property named DisplayText and also call RaisePropertyChanged("CanCancel"); but even after that my "Clear Text" button is not enabled. What can be the reason behind it ? My textblock shows the text value but the "clear text" button is still not enabled.
There's a bit mixing up going on in your example, as far as I can tell: You basically don't use the built-in 'CanExecute' mechanism of 'RelayCommand', but rebuild it yourself while still defining the CanExecute method of the 'RealyCommand'. The idea of 'CanExecute' is to automatically disbale controls whose command can't execute, so you don't need to do it manually. Returning 'true' in an 'CanExecute' method doesn't really make sense, as you don't necessarily need to have a CanExecute delegate in your RelayCommand (... = new RelayCommand(ExecuteCommand); is fine). Your scenario doesn't work because you're not calling 'RaisCanExecuteChanged()' on 'CancelCommand'.
Try the following implementation, I've removed the redundancies and inserted the missing 'RaiseCanExecuteChanged()'. See the comments for explanations:
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}" />
</StackPanel>
And use this simplified ViewModel:
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(ExecuteSetText);
CancelCommand = new RelayCommand(ExecuteCancel, CanExecuteCancel);
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
// Raise the CanExecuteChanged event of CancelCommand
// This makes the UI reevaluate the CanExecuteCancel
// Set a breakpoint in CanExecuteCancel method to make
// sure it is hit when changing the text
CancelCommand.RaiseCanExecuteChanged();
}
}
private bool CanExecuteCancel()
{
// You can simplify the statement like this:
return DisplayText != "";
}
private void ExecuteCancel()
{
DisplayText = "";
}
private void ExecuteSetText()
{
DisplayText = "Hello";
}
}