Lost in MVVM, Trying to connect M to VM to V - c#

So here is what i am trying to do. I want to pass the string techName from the model to the view... and I am not understanding how to do this. I can do it from the ViewModel to the view just fine but I want the data to sit in the Model and get pulled by the ViewModel and pass to the View. so that the data can be used across multiple views. That being said here is what I have
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DefaultViewClicked(object sender, RoutedEventArgs e)
{
DataContext = new DefaultViewModel();
}
private void NewCallClicked(object sender, RoutedEventArgs e)
{
DataContext = new NewCallViewModel();
}
}
View:
Text="{Binding model.TechName}
ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using callFlow.Models;
namespace callFlow.ViewModels
{
public class DefaultViewModel
{
private ObservableCollection<DataModel> model = new ObservableCollection<DataModel>();
public DefaultViewModel() { }
private DataModel _selectedModel;
public DataModel SelectedModel
{
get { return _selectedModel ?? (_selectedModel = new SelectedModel()); }
set { _selectedModel = value; }
}
public void changeSelectedModel(DataModel newSelectedModel)
{
SelectedModel.TechName = newSelectedModel.TechName;
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Drawing;
namespace callFlow.Models
{
public class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string techName ="this is a test";
public DataModel()
{
}
public string TechName
{
get { return techName; }
set { techName = value; OnPropertyChanged(); }
}
protected void OnPropertyChanged([CallerMemberName] string techName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(techName));
}
}
}

Your Datacontext class DefaultViewModel does not have any model property.
So you have to replace your XAML with this:
Text="{Binding SelectedModel.TechName}

Related

Change value for anonymous property of type string

I'm working on an app written in WPF, the code is written in C#.
I have a question mark icon which when pressed suppose to set content to specific label.
The label content is binding to a property in the view model, lets call it 'NoneLegend'.
I want that property to clear itself after 5 second so I have a utility class that suppose to manage that. Inside that class I wrote an anonymous method that gets any type of property.
My question is how do I set that property to string.empty?
The method looks like this:
public static void EmptyStringAfterXseconds<T>(Expression<Func<T>> property)
{
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}
else
{
var t = propertyInfo.GetType();
propertyInfo.SetValue(null, "");
}
}
And I'm calling it like that:
NoneLegend = "Bla bla...";
Utils.EmptyStringAfterXseconds(() => NoneLegend);
How about this. I'm a bit worried about the new ResetAfterTime() call. Don't know if the instance is around long enough. It might be collected by the Garbage Collector before the Timer fires. Would have to think about that but it seems to work fine.
A Notifier class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace WpfApp1
{
public class PropertyNotifier : INotifyPropertyChanged
{
public void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// The view automatically fills the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
}
}
The Viewmodel:
using System.ComponentModel;
using System.Threading;
namespace WpfApp1
{
public class ViewModel : PropertyNotifier
{
public ViewModel(string legend)
{
Legend = legend;
}
private string _Legend;
/// <summary>
/// Here's where the magic happens
/// </summary>
public string Legend
{
get => _Legend;
set
{
_Legend = value;
new ResetAfterTime<string>(this, () => Legend, value);
}
}
}
}
And your enhanced routine:
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace WpfApp1
{
public class ResetAfterTime<T>
where T : class
{
Timer _timer;
PropertyInfo _propertyInfo;
PropertyNotifier _notifier;
int _due;
public ResetAfterTime(PropertyNotifier notifier, Expression<Func<T>> property, T value, int due = 3000)
{
_notifier = notifier;
_propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
_due = due;
if (_propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}
else
{
if (value != default)
{
StartTimer();
}
}
}
private void StartTimer()
{
_timer = new Timer(MakeDisappear, null, _due, _due);
}
private void StopTimer()
{
_timer.Dispose();
_timer = null;
}
private void MakeDisappear(object state)
{
SetValue(null);
StopTimer();
}
private void SetValue(object value)
{
var t = _propertyInfo.GetType();
_propertyInfo.SetValue(_notifier, value);
_notifier.NotifyPropertyChanged(_propertyInfo.Name);
}
}
}
For the binding in the view to know what has changed you need to notify it about that change. The INotifyProperyChanged interface was designed for that purpose. In the code below in the ViewModel this interface is implemented.
I've created a simple WPF program. Here's the MainWindow:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<Label x:Name="Legend" Content="{Binding Legend}"></Label>
</StackPanel>
</Grid>
</Window>
And the code behind:
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel("Text should disappear");
}
}
}
And the ViewModel:
using System.ComponentModel;
using System.Threading;
namespace WpfApp1
{
public class ViewModel : INotifyPropertyChanged
{
Timer LegendTimer;
public ViewModel(string legend)
{
Legend = legend;
}
private void StartLegendTimer()
{
LegendTimer = new Timer(MakeDisappear, null, 3000, 3000);
}
private void StopLegendTimer()
{
LegendTimer.Dispose();
LegendTimer = null;
}
private void MakeDisappear(object state)
{
Legend = string.Empty;
StopLegendTimer();
}
private string _Legend;
/// <summary>
/// Here's where the magic happens
/// </summary>
public string Legend
{
get => _Legend;
set
{
// Each time the value is set ...
_Legend = value;
// we notify the view that a value has changed
NotifyPropertyChanged(nameof(Legend));
// If the value is not null or empty
if(!string.IsNullOrWhiteSpace(value))
{
// We start the time
StartLegendTimer();
}
}
}
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// The view automatically fills the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
}
}

UWP NavigationView: Two Views under one NavigationViewItem

I am trying to create a NavigationViewItem which navigates to two different Views based on a condition.
The UWP app was created using Windows Template Studio and already has the NavigationView and default functions to navigate between pages. I want to have the app check for a condition and then either navigate to one or the other View if the user clicks on the NavigationViewItem. Also the app should keep the colored line, indicating which View it is on, on the one single NavigationViewItem.
This is how my NavigationView currently looks like. I would like to combine the SetTimer and WatchTimer Views. I unfortunately have no idea how the underlying code works as it was all automatically generated and I don't really understand it.
<winui:NavigationView.MenuItems>
<winui:NavigationViewItem x:Uid="Shell_SetTimer" helpers:NavHelper.NavigateTo="views:SetTimerPage" />
<winui:NavigationViewItem x:Uid="Shell_WatchTimer" helpers:NavHelper.NavigateTo="views:WatchTimerPage" />
<winui:NavigationViewItem x:Uid="Shell_History" helpers:NavHelper.NavigateTo="views:HistoryPage" />
</winui:NavigationView.MenuItems>
This is the code behind:
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using ShutdownTimer.Helpers;
using ShutdownTimer.Services;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Navigation;
using WinUI = Microsoft.UI.Xaml.Controls;
namespace ShutdownTimer.Views
{
public sealed partial class ShellPage : Page, INotifyPropertyChanged
{
private readonly KeyboardAccelerator _altLeftKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu);
private readonly KeyboardAccelerator _backKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.GoBack);
private bool _isBackEnabled;
private WinUI.NavigationViewItem _selected;
public bool IsBackEnabled
{
get { return _isBackEnabled; }
set { Set(ref _isBackEnabled, value); }
}
public WinUI.NavigationViewItem Selected
{
get { return _selected; }
set { Set(ref _selected, value); }
}
public ShellPage()
{
InitializeComponent();
DataContext = this;
Initialize();
}
private void Initialize()
{
NavigationService.Frame = shellFrame;
NavigationService.NavigationFailed += Frame_NavigationFailed;
NavigationService.Navigated += Frame_Navigated;
navigationView.BackRequested += OnBackRequested;
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
// Keyboard accelerators are added here to avoid showing 'Alt + left' tooltip on the page.
// More info on tracking issue https://github.com/Microsoft/microsoft-ui-xaml/issues/8
KeyboardAccelerators.Add(_altLeftKeyboardAccelerator);
KeyboardAccelerators.Add(_backKeyboardAccelerator);
await Task.CompletedTask;
}
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw e.Exception;
}
private void Frame_Navigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = navigationView.SettingsItem as WinUI.NavigationViewItem;
return;
}
Selected = navigationView.MenuItems
.OfType<WinUI.NavigationViewItem>()
.FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private bool IsMenuItemForPageType(WinUI.NavigationViewItem menuItem, Type sourcePageType)
{
var pageType = menuItem.GetValue(NavHelper.NavigateToProperty) as Type;
return pageType == sourcePageType;
}
private void OnItemInvoked(WinUI.NavigationView sender, WinUI.NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
NavigationService.Navigate(typeof(SettingsPage));
return;
}
var item = navigationView.MenuItems
.OfType<WinUI.NavigationViewItem>()
.First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageType = item.GetValue(NavHelper.NavigateToProperty) as Type;
NavigationService.Navigate(pageType);
}
private void OnBackRequested(WinUI.NavigationView sender, WinUI.NavigationViewBackRequestedEventArgs args)
{
NavigationService.GoBack();
}
private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null)
{
var keyboardAccelerator = new KeyboardAccelerator() { Key = key };
if (modifiers.HasValue)
{
keyboardAccelerator.Modifiers = modifiers.Value;
}
keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked;
return keyboardAccelerator;
}
private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
var result = NavigationService.GoBack();
args.Handled = result;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And the NavHelper
using System;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
namespace ShutdownTimer.Helpers
{
public class NavHelper
{
// This helper class allows to specify the page that will be shown when you click on a NavigationViewItem
//
// Usage in xaml:
// <winui:NavigationViewItem x:Uid="Shell_Main" Icon="Document" helpers:NavHelper.NavigateTo="views:MainPage" />
//
// Usage in code:
// NavHelper.SetNavigateTo(navigationViewItem, typeof(MainPage));
public static Type GetNavigateTo(NavigationViewItem item)
{
return (Type)item.GetValue(NavigateToProperty);
}
public static void SetNavigateTo(NavigationViewItem item, Type value)
{
item.SetValue(NavigateToProperty, value);
}
public static readonly DependencyProperty NavigateToProperty =
DependencyProperty.RegisterAttached("NavigateTo", typeof(Type), typeof(NavHelper), new PropertyMetadata(null));
}
}
Based on the code you post, the navigation happens in the OnItemInvoked event. If you want to have conditions to control the process. That will be the right place. You could set different pagetype in NavigationService.Navigate(pageType) according to different conditions.
Besides, I do not recommend you to use the code directly if you don't understand it. The codes contains all the functions but it also means you don't know why and how it works. It might be a better practice if you could try to implement the function by yourself using native code. After you know more about the navigation, then you could understand these code more easily.

How to make logs

To explain a little more, I have a Main Form which contains a large list of jobs.
Every item in the list is a instance of my class called Jobs.
When an item is clicked, another Form is opening, in which user can edit information of selected job. I pass a job object from Main Form to details Form and edit it through TextBoxes, ComboBoxes and so on.
Now I need to detect which properties of jobs have changed and write it in log file. I know how to write to log file, but I dont know how to detect which properties have changed.
I could go and write 30 if statements in which I would compare starting point with ending point but I have 30 properties and it would be a complete mess.
Any ideas?
Take a look at INotifyPropertyChanged:https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface
Example
using log4net;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(Form1).FullName);
public PersonViewPresenter Presenter { get; private set; }
public Form1()
{
InitializeComponent();
Presenter = new PersonViewPresenter();
Presenter.PropertyChanged += Presenter_PropertyChanged;
AddBindings();
}
private void Presenter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Logger.Info($"Property changed {e.PropertyName}");
}
private void AddBindings()
{
_firstnameTextbox.DataBindings.Add(new Binding(nameof(_firstnameTextbox.Text), Presenter, nameof(Presenter.FirstName), false, DataSourceUpdateMode.OnValidation));
_lastnameTextBox.DataBindings.Add(new Binding(nameof(_lastnameTextBox.Text), Presenter, nameof(Presenter.LastName), false, DataSourceUpdateMode.OnValidation));
}
}
}
ViewPresenter implementation
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WindowsFormsApp1
{
public class PersonViewPresenter : ViewPresenterBase
{
private string _lastName;
private string _firstName;
public string FirstName
{
get => _firstName; set
{
if (_firstName != value)
{
_firstName = value;
NotifyPropertyChanged();
}
}
}
public string LastName
{
get => _lastName; set
{
if (_lastName != value)
{
_lastName = value;
NotifyPropertyChanged();
}
}
}
}
public abstract class ViewPresenterBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Doesn't recognize imports of interface in the same namespace? seems like MainViewModel is isolated from other classes in the namespace C#

I am making a reversi game in C# with visual studio. I have a ViewModel namespace with a MainViewModel.cs class, where i create an interface Screen. When I want to use this interface in other classes like WelcomeViewModel.cs, which is in the same namespace, it doesn't recognize Screen. Below some sample of my code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Model.Reversi;
using View;
namespace ViewModel
{
class MainViewModel: INotifyPropertyChanged
{
private Screen screen;
public event Action ExitApp;
public event PropertyChangedEventHandler PropertyChanged;
public Screen Screen
{
get { return this.screen; }
set { this.screen = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Screen))); }
}
internal void Exit()
{
ExitApp?.Invoke();
}
public MainViewModel()
{
this.Screen = new WelcomeViewModel(this);
}
}
public abstract class Screen
{
protected readonly MainViewModel mainModel;
protected Screen(MainViewModel mainModel)
{
this.mainModel = mainModel;
}
protected void Switch(Screen screen)
{
this.mainModel.Screen = screen;
}
}
public class ScreenWelcome : Screen
{
public ScreenWelcome(MainViewModel navigator) : base(navigator)
{
GoToGame = new EasyCommand(() => Switch(new ScreenGame(navigator)));
}
public ICommand GoToGame { get; }
}
public class ScreenGame : Screen
{
public ScreenGame(MainViewModel navigator) : base(navigator)
{
GoToWelcome = new EasyCommand(() => Switch(new ScreenWelcome(navigator)));
}
public ICommand GoToWelcome { get; }
}
}
WelcomeViewModel class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using ViewModel.Screen;
using Model.Reversi;
namespace ViewModel
{
class WelcomeViewModel: Screen
{
private int width;
private int height;
public int Width
{
get { return width; }
set {
if (ReversiBoard.IsValidWidth(value))
this.width = value;
}
}
public int Height
{
get { return height; }
set
{
if (ReversiBoard.IsValidHeight(value))
this.height = value;
}
}
public WelcomeViewModel(MainViewModel mainModel) : base(mainModel)
{
this.Width = 9;
this.Height = 9;
this.Options = new PlayerOptionsViewModel();
Start = new EasyCommand(() =>
{
Switch(new GameViewModel(this.mainModel, Options, Width, Height));
});
}
public PlayerOptionsViewModel Options { get; set; }
public ICommand Start { get; }
}
}
the : Screen stays white so it means it doesn't recognize the Screen class. How is this possible?
Anyone has a solution? [enter image description here][1]
You are most likely getting
Error CS0051 Inconsistent accessibility: parameter type 'MainViewModel' is less accessible than method 'ScreenWelcome.ScreenWelcome(MainViewModel)'
In the public class ScreenWelcome you have the constructor public ScreenWelcome(MainViewModel navigator). This constructor exposes MainViewModel in its public interface, therefore class MainViewModel must be public as well. Make it public
public class MainViewModel : INotifyPropertyChanged
{
...
}

C# condense redundant code in class with INotifyPropertyChange

I've been reading a few articles the past few days on inheriting classes and creating base classes, which i often do in tools I've written. However I specifically was looking into ways to reduce the redundant code often written in my classes which contain INotifyPropertyChange. Normally my classes look something like this, inheriting the base class of NotifyBase. However I've seen here a there in various scripts people moving some of the Get and Set code into the base class. I wanted to know what things to look out for when doing this? is this bad practice or good practice? Does the example I provided written correctly to do so?
One benefit being the Get and Set are much simpler in the classes which inherit NotifyBase in the new setup.
Current Setup Example
FileItem Class
using System.IO;
namespace WpfApplication1
{
public class FileItem : NotifyBase
{
private string _fullPath;
public string FullPath
{
get { return this._fullPath; }
set
{
this._fullPath = value;
this.NotifyPropertyChanged("FullPath");
}
}
}
}
Base Class 'NotifyBase'
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Possible New Setup
FileItem Class
using System.IO;
namespace WpfApplication1
{
public class FileItem : NotifyBase
{
public string FullPath
{
get { return Get<string>(); }
set { Set(value); }
}
}
}
Base Class 'NotifyBase'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Varo.Helper
{
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private readonly Dictionary<string, object> _propertyValues;
protected NotifyBase()
{
_propertyValues = new Dictionary<string, object>();
}
protected void Set<T>(T value, [CallerMemberName] string name = "")
{
if (_propertyValues.ContainsKey(name))
{
_propertyValues[name] = value;
NotifyPropertyChanged(name);
}
else
{
_propertyValues.Add(name, value);
NotifyPropertyChanged(name);
}
}
protected T Get<T>([CallerMemberName] string name = "")
{
if (_propertyValues.ContainsKey(name))
{
return (T)_propertyValues[name];
}
return default(T);
}
}
}
Check the PropertyChanged.Fody NuGet
Weaves .NET assembly at compile time so properties declared as
public int Property { get; set; }
Gets compiled as
private int property;
public int Property
{
get
{
return property;
}
set
{
if(property != value)
{
property = value;
if (this.PropertyChanged!= null) PropertyChanged(this, new PropertyChangedEventArgs("Property");
}
}
}

Categories

Resources