WPF data templates - c#

I'm getting started with WPF and trying to get my head around connecting data to the UI. I've managed to connect to a class without any issues, but what I really want to do is connect to a property of the main window.
Here's the XAML:
<Window x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>
Here's the code for the main window:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
}
Here's the Platform class:
public class Platform
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
public bool Selected
{
get { return m_selected; }
set { m_selected = value; }
}
}
This all compiles and runs fine but the list box displays with nothing in it. If I put a breakpoint on the get method of Platforms, it doesn't get called. I don't understand as Platforms is what the XAML should be connecting to!

Your code looks ok apart from the fact that the Binding on Source on CollectionViewSource is not correct. You probably meant this:
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=MainWindow.Platforms}"
x:Key="platforms"/>
Without this change the Binding actually looked for property Platforms on Application instance.

I would suggest you add the platforms not to the MainWindow but rather set it as the MainWindow's DataContext (wrapped inside a ViewModel).
That way you can very easily bind against it (the binding code would look like ItemsSource={Binding Path=Platforms}).
This is part of WPFs design, that every form should have a explicit DataContext it binds to.

A somewhat more appropriate solution is to give your window a name. A nice convention is _this.
<Window x:Name="_this" x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate DataType="{x:Type custom:Platform}">
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>

Here's my updated code, the XAML:
<Window x:Name="_this"
x:Class="test3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test3"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding ElementName=_this, Path=Platforms}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
The code behind the XAML:
public partial class MainWindow : Window
{
ObservableCollection<Platform> m_platforms;
public MainWindow()
{
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
InitializeComponent();
}
public ObservableCollection<Platform> Platforms
{
get { return m_platforms; }
set { m_platforms = value; }
}
private void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
private void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
}
...and finally the Platform code, enhanced to finish off the two way interaction:
public class Platform : INotifyPropertyChanged
{
private string m_name;
private bool m_selected;
public Platform(string name)
{
m_name = name;
m_selected = false;
}
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged("Name");
}
}
public bool Selected
{
get { return m_selected; }
set
{
m_selected = value;
OnPropertyChanged("Selected");
}
}
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

Using DataContext, it gets even easier!
<Window x:Class="test5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:test5"
Title="MainWindow" Height="190" Width="177">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=.}"
x:Key="platforms"/>
<DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
<TextBlock Margin="1" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="23" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Source={StaticResource platforms}}"
ItemTemplate="{StaticResource platformTemplate}"/>
<Button Click="OnBuild" Grid.Row="1">Build...</Button>
<Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
</Grid>
</Window>
Here's the code behind this:
private ObservableCollection<Platform> m_platforms;
public MainWindow()
{
InitializeComponent();
m_platforms = new ObservableCollection<Platform>();
m_platforms.Add(new Platform("PC"));
m_platforms.Add(new Platform("PS3"));
m_platforms.Add(new Platform("Xbox 360"));
DataContext = m_platforms;
}
public void OnBuild(object sender, RoutedEventArgs e)
{
string text = "";
foreach (Platform platform in m_platforms)
{
if (platform.Selected)
{
text += platform.Name + " ";
}
}
if (text == "")
{
text = "none";
}
MessageBox.Show(text, "WPF TEST");
}
public void OnTogglePC(object sender, RoutedEventArgs e)
{
m_platforms[0].Selected = !m_platforms[0].Selected;
}
Note that I've dropped the need to declare Platforms as a property of the main window, instead I assign it to the DataContext, and the XAML source becomes, simply, "."

Related

MVVM Property Change in Behavior<TreeView>

I cannot get a change in the UI (TextBox) when selecting a tree item. When I select a tree item, the property and the text should change. But this does not happen. Property changes, but text remains the same. At first I tried to cause a property change in MainViewModel, but ran into the same problem: the property changes but the UI remains unchanged.
public class BindableSelectedItemBehavior : Behavior<TreeView>, INotifyPropertyChanged
{
MainViewModel vm = new MainViewModel();
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
string name = GetName((Item)SelectedItem);
Message = "edgddgdgdg";
}
public string GetName(Item item)
{
string circ = item.Name;
return circ;
}
}
}
<UserControl x:Class="WpfApp3.TreeViewTextChange"
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:local="clr-namespace:WpfApp3"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:BindableSelectedItemBehavior/>
</UserControl.DataContext>
<Grid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Path=Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Background="Aqua" Width="200" Height="50"/>
</Grid>
</UserControl>
Used a debugger - property changes but does not change textbox in UserControl.
My MainViewModel
class MainViewModel : BindableBase
{
MainModel mainModel = new MainModel();
private ICollectionView root;
public MainViewModel()
{
OpenSth = new DelegateCommand(DisplayItemTree);
ShowCheck = new DelegateCommand(ShowCheckedItemsCommand);
Sth = new DelegateCommand(Changetext);
TextChengedCommand = new DelegateCommand<object>(textChange);
}
public ICommand OpenSth { get; }
public ICommand ShowCheck { get; }
public ICommand Sth { get; }
public ICommand TextChengedCommand { get; }
private void textChange(object obj)
{
var str = obj as string;
}
public void DisplayItemTree()
{
var itemTree = mainModel.GetItemsTree();
Root = CollectionViewSource.GetDefaultView(itemTree);
Root.Filter = new Predicate<object>((item) =>
{
var realItem = (Item)item;
return true;
});
Root.Refresh();
}
public ICollectionView Root
{
get => root;
set => SetProperty(ref root, value);
}
public void ShowCheckedItemsCommand()
{
var str = new StringBuilder();
List<Item> items = mainModel.FindCheckedItem();
if (items.Count == 0)
{
str.Append("No selected items");
}
else
{
foreach (var item in items)
{
str.Append(item.Name);
}
}
MessageBox.Show(str.ToString());
}
private string _name;
public string TextText
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("TextText");
}
}
public void Changetext()
{
TextText = "dggg";
}
}
}
And xaml code
<Window x:Class="WpfApp3.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:WpfApp3"
xmlns:beh="clr-namespace:WpfApp3.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ii="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
xmlns:bh="clr-namespace:WpfApp3.ViewModel"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
<TextBox x:Name="searchBox" DockPanel.Dock="Top" Height="30" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1" Text="{Binding SearchText}">
<ii:Interaction.Triggers>
<ii:EventTrigger EventName="TextChenged">
<ii:InvokeCommandAction Command="{Binding TextChengedCommand}" CommandParameter="{Binding ElementName=searchBox, Path=Text}"/>
</ii:EventTrigger>
</ii:Interaction.Triggers>
</TextBox>
<DockPanel DockPanel.Dock="Bottom">
<Button Content="Get TreeView" Command="{Binding OpenSth}" Width="100"/>
<Button Content="Show Check Item" Command="{Binding ShowCheck}" Width="100"/>
</DockPanel>
<TreeView BorderBrush="Black" BorderThickness="1" ItemsSource="{Binding Root}">
<i:Interaction.Behaviors>
<beh:BindableSelectedItemBehavior SelectedItem="{Binding SelectedNode, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Sub}">
<DockPanel>
<CheckBox IsChecked="{Binding CheckedItem}" Margin="0 8 0 8" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
<DataGrid AutoGenerateColumns="False" Height="210" HorizontalContentAlignment="Left" Name="DataGrid" VerticalAlignment="Top" Grid.Column="2" Grid.Row="0" Margin="0,0,0,0" ItemsSource="{Binding ListOfCircuets}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Length}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding dU}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding Cable}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding I}" MinWidth="50"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding TextText, Mode=TwoWay}" Background="Aqua" Width="200" Height="50"/>
<Button Content="Show Check Item" Command="{Binding Sth}" Width="100"/>
<local:TreeViewTextChange Grid.Row="1" Grid.Column="2"></local:TreeViewTextChange>
</Grid>
</Window>

Closing an Open Window Using MVVM Pattern, Produces System.NullReferenceException error

I'm trying to learn MVVM pattern using WPF C#. And I'm running into an error when trying to close an opened window after saving information to an sqlite database. When the command to save a new contact is raised, I am getting an error on HasAddedContact(this, new EventArgs());
Error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
My ViewModel:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs code behind:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
Adding additional code where I call the new window:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
You're creating NewContactWindow's viewmodel in the constructor, correctly assigning it to DataContext, and correctly adding a handler to that event. Unfortunately, you also create a second instance of the same viewmodel in resources, and you manually set the Source property of all the bindings to use the one in the resources, which doesn't have the event handler.
Window.DataContext, which you set in the constructor, is the default Source for any binding in the Window XAML. Just let it do its thing. I also removed all the redundant Mode=TwoWay things from the Bindings to TextBox.Text, since that property is defined so that all bindings on it will be TwoWay by default. I don't think UpdateSourceTrigger=PropertyChanged is doing anything necessary or helpful either: That causes the Binding to update your viewmodel property every time a key is pressed, instead of just when the TextBox loses focus. But I don't think you're doing anything with the properties where that would matter; there's no validation or anything. But TextBox.Text is one of the very few places where that's actually used, so I left it in.
You should remove the analagous viewmodel resource in your other window. It's not doing any harm, but it's useless at best. At worst, it's an attractive nuisance. Kill it with fire and bury the ashes under a lonely crossroads at midnight.
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>

C# WPF Update Status bar text and progress from another window

I have a Main window named "wpfMenu" With a status bar which contains a text block and progress bar. The status bar needs to be updated from methods which are running on separate windows launched from the Main window (only one window open at any time).
Preferably I would like to pass the min, max, progress, text values to a class called "statusUpdate" to update the progress but i have no idea where to begin and any examples of updating progress bars I've come across are running on the same window.
Here is my code for the Status bar so far
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Custom="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon" x:Class="Mx.wpfMenu"
Title="Mx - Menu" Height="600" Width="1000" Background="#FFF0F0F0" Closed="wpfMenu_Closed">
<Grid>
<StatusBar x:Name="sbStatus" Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Name="sbMessage" Text="{Binding statusUpdate.Message}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Name="sbProgress" Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
The code for my class is
public class statusUpdate : INotifyPropertyChanged
{
private string _message;
public event PropertyChangedEventHandler PropertyChanged;
public statusUpdate()
{
}
public statusUpdate (string value)
{
this._message = value;
}
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string _message)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(_message));
}
}
}
There are several steps to this, but they're all well documented elsewhere. It might seem like a complex process, but it's something you'll do over and over in WPF.
You're right to store all the settings in a class. However this class needs to implement INotifyPropertyChanged and raise a PropertyChanged event in every property setter.
using System.ComponentModel;
public class StatusUpdate : INotifyPropertyChanged
{
private string message;
public event PropertyChangedEventHandler PropertyChanged;
public StatusUpdate()
{
}
public string Message
{
get { return this.message; }
set
{
this.message = value;
this.OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can make it a public property of your code-behind class, and bind your progress bar properties to it.
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public StatusUpdate Status { get; set; } = new StatusUpdate();
private void PlayCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public void PlayCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Play";
}
public void StopCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Stop";
}
}
Then you can pass a reference to the same class to the child forms, and when they set any of the properties, WPF will catch the event and update the GUI.
Let me know if you can't find an example for any of those steps.
Here's a version of your XAML with the binding and buttons I used for the above example:
<Window x:Class="WpfProgressBar.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:WpfProgressBar"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="Play" Executed="PlayCommand_Executed" CanExecute="PlayCommand_CanExecute" />
<CommandBinding Command="Stop" Executed="StopCommand_Executed" />
</Window.CommandBindings>
<Grid>
<StackPanel>
<Button Content="Play" Command="Play" />
<Button Content="Stop" Command="Stop" />
</StackPanel>
<StatusBar Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Text="{Binding Status.Message, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, NotifyOnSourceUpdated=True}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
NB, I wouldn't normally do command bindings like this, but didn't want to get into the complications of adding RelayCommand

Unable to programmatically read an updated user control property from within MainWindowViewModel

I've following code in my WPF app.I'm dynamically rendering the user control in MainWindow using ContentPresenter control on change of SelectedAccountType combobox value.
I enter some value in MyProperty textbox in UI and click on Save button on MainWindow.But I dont see this value in the MainWindowViewModel.cs in the Save method(i.e. on click of Save button).
All my ViewModels extend this abstract class:
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
What am I missing here please?
Thanks.
Here's my code:
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MainViewModel="clr-namespace:Test.ViewModel"
xmlns:ViewModel="clr-namespace:Test.ViewModel.AccountTypes"
xmlns:View="clr-namespace:Test.View" x:Class="Test.MainWindow"
xmlns:Views="clr-namespace:Test.View.AccountTypes"
xmlns:v="clr-namespace:Test.View.AccountTypes"
xmlns:vm="clr-namespace:Test.ViewModel.AccountTypes"
Title="{Binding DisplayName, Mode=OneWay}" ResizeMode="CanResize" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<MainViewModel:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal" Height="28" Width="auto" Margin="5,0,0,0">
<ComboBox Width="360" Margin="1,0" ItemsSource="{Binding AccountTypes}" DisplayMemberPath="Code" SelectedValuePath="ID" SelectedItem="{Binding SelectedAccountType,
Mode=TwoWay}" TabIndex="0" />
</StackPanel>
<ContentPresenter Content="{Binding CurrentViewModel}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type ViewModel:AC1ViewModel}">
<Views:AC1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AC2ViewModel}">
<Views:AC2View/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
MainWindowViewModel.cs
public object CurrentViewModel
{
get
{
return m_currentViewModel;
}
set
{
m_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
public AccountType SelectedAccountType
{
get
{
return m_selectedSearchAccountType;
}
set
{
m_selectedSearchAccountType = value;
if (SelectedAccountType.Code == "AC1")
{
CurrentViewModel = new AC1ViewModel();
}
else if (SelectedAccountType.Code == "AC2")
{
CurrentViewModel = new AC2ViewModel();
}
}
}
public void Save()
{
Type sourceType = CurrentViewModel.GetType();
PropertyInfo[] sourcePI = sourceType.GetProperties();
Type destinationType = securityDetails.GetType();
PropertyInfo[] destinationPI = destinationType.GetProperties();
string propertyName = string.Empty;
object propertyValue = null;
foreach (var pinfo in sourcePI)
{
propertyName = pinfo.Name.Trim();
propertyValue = pinfo.GetValue(CurrentViewModel)
}
}
AC1View.xaml:
<TextBox HorizontalAlignment="Left" Height="23" Margin="1,1,0,0" VerticalAlignment="Top" Width="230" TabIndex="1" Text="{Binding MyProperty,UpdateSourceTrigger=PropertyChanged}" />
AC1ViewModel.cs
public class AC1ViewModel
{
private string m_myProperty = "";
public AC1ViewModel()
{
}
public string MyProperty
{
get
{
return m_myProperty;
}
set
{
m_myProperty = value;
}
}
}

Show Different UI elements when selecting TreeView Items

I am new with WPF - I want to create a tester for my server
I want to have on the right side of the application a TreeView and whenever a user selects a node - appropriate items is shown on the right side. For example I have a Connection node and under it many Sessions nodes, The Connection and Session have different parameters. I have built the tree view using mvvm and all works fine, but how can I achieve the second goal?
the xaml
<Window x:Class="Tree1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self ="clr-namespace:Tree1"
xmlns:models ="clr-namespace:Tree1.Models"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:TestPlanViewModel}" ItemsSource="{Binding Connections}">
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type self:ConnectionViewModel}" ItemsSource="{Binding Sessions}">
<StackPanel>
<TextBlock Text="Connection" Margin="10, 0, 0,0"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:SessionViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,0,0" Text="Session"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="10">
<Button Height="23" VerticalAlignment="Top" Margin="277,10,144,0" Name="addSessionBtn" Width="76"></Button>
<TreeView Name="testPlanTview" Margin="0,10,283,0" SelectedItemChanged="testPlanTview_SelectedItemChanged">
<TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
</TreeViewItem>
</TreeView>
</Grid>
You can use DataTemplates. There are two possible solution for your problem.
First of all let's create a base class:
public abstract class SelectableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isSelected;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool IsSelected
{
get
{
return isSelected;
}
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
}
For our example we have two classes:
Car, whose properties are "Hp" (int) and "Matriculation" (DateTime)
Person, whose properties are "Name" (string) and "Surname" (string)
Both of them extend SelectableObject.
Now the ViewModel (of course it is just a sample):
public class ViewModel : SelectableObject
{
private ArrayList tree = new ArrayList();
private ObjectWrapper current;
private Car car = new Car();
private Person person = new Person();
public ViewModel()
{
car.Hp = 120;
car.Matriculation = DateTime.Today;
car.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
person.Name = "John";
person.Surname = "Doe";
person.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
tree.Add(car);
tree.Add(person);
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
SelectableObject impl = (SelectableObject)sender;
if (e.PropertyName == "IsSelected" && impl.IsSelected)
{
Current = new ObjectWrapper(impl);
}
}
public ObjectWrapper Current
{
get
{
return current;
}
private set
{
current = value;
OnPropertyChanged("Current");
}
}
public IEnumerable Tree
{
get
{
return tree;
}
}
}
The ViewModel uses a the ObjectWrapper class:
public class ObjectWrapper
{
private readonly object wrappedInstance;
private readonly ReadOnlyCollection<PropertyWrapper> propertyWrappers;
public ObjectWrapper(object instance)
{
Collection<PropertyWrapper> collection = new Collection<PropertyWrapper>();
Type instanceType;
wrappedInstance = instance;
instanceType = instance.GetType();
foreach (PropertyInfo propertyInfo in instanceType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
{
collection.Add(new PropertyWrapper(instance, propertyInfo));
}
propertyWrappers = new ReadOnlyCollection<PropertyWrapper>(collection);
}
public ReadOnlyCollection<PropertyWrapper> PropertyWrappers
{
get
{
return propertyWrappers;
}
}
public object Instance { get { return wrappedInstance; } }
}
public class PropertyWrapper
{
private readonly object instance;
private readonly PropertyInfo propertyInfo;
public PropertyWrapper(object instance, PropertyInfo propertyInfo)
{
this.instance = instance;
this.propertyInfo = propertyInfo;
}
public string Label
{
get
{
return propertyInfo.Name;
}
}
public Type PropertyType
{
get
{
return propertyInfo.PropertyType;
}
}
public object Value
{
get
{
return propertyInfo.GetValue(instance, null);
}
set
{
propertyInfo.SetValue(instance, value, null);
}
}
}
First solution (the easiest one)
You can use implicit datatemplating:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="600">
<DockPanel>
<TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<ContentControl Content="{Binding Path=Current.Instance, Mode=OneWay}" Margin="5">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Car}">
... define your car template here ...
</DataTemplate>
<DataTemplate DataType="{x:Type local:Person}">
... define your person template here ...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>
Second solution (imho the best one)
You can take advantage of ObjectWrapper object, by using an ItemsControl (here I used Extended WPF Toolkit for DateTime and Int controls):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="600">
<Window.Resources>
<local:ItemTemplateSelector x:Key="ItemTemplateSelector" />
<DataTemplate x:Key="{x:Type sys:String}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<TextBox Text="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="{x:Type sys:DateTime}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<toolkit:DateTimePicker Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="{x:Type sys:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
<toolkit:IntegerUpDown Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<ItemsControl ItemsSource="{Binding Path=Current.PropertyWrappers, Mode=OneWay}"
Margin="5" ItemTemplateSelector="{StaticResource ItemTemplateSelector}" />
</DockPanel>
</Window>
The implementation of the ItemTemplateSelector it is not difficult:
public class ItemTemplateSelector : DataTemplateSelector
{
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
PropertyWrapper propertyWrapper = (PropertyWrapper)item;
FrameworkElement frameworkElement = (FrameworkElement)container;
DataTemplate dataTemplate = (DataTemplate)frameworkElement.TryFindResource(propertyWrapper.PropertyType);
return dataTemplate;
}
}
My answer is quite long, but I wanted to show you both roads you can use.
Of course you can improve the PropertyWrapper by using attributes.
If you're already using MVVM i would sugest something like System.Windows.Interactivity and Prism regions to implement what you need.
For example:
Xaml:
<TreeView Name="testPlanTview" Margin="0,10,283,0">
<TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding OpenNewViewCommand}"
CommandParameter="{Binding SelectedItem,ElementName=testPlanTview}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeViewItem>
</TreeView>
<ContentControl prism:RegionManager.RegionName="MyRegion"/>
Viewmodel:
public ICommand OpenNewViewCommand
{
get { return this.selectedCommand; }
}
In the view model constructor you add:
this.selectedCommand = new DelegateCommand<YourModel>(this.SelectedExecute);
And the command:
private void SelectedExecute(YourModel parameter)
{
this.regionManager.RequestNavigate(RegionNames.MyRegion, new Uri("ViewToNavigate", UriKind.Relative), parameters);
}
Please be aware that this is just an example on how to make the navigation possible with prism. For more information on what i'm suggesting you can check the msdn link here

Categories