WPF TabControl - Add New Tab With Content Template - c#

I am trying to make a demo application to help me understand WPF/MVVM. I have been struggling for 3 days looking at various tutorials and threads. I want to make a tab control with a new tab button (like here) that lets the user create a new tab with specified content template. I create my user control that I want to be the template here:
<UserControl x:Class="MvvmTest.UserControl1"
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:MvvmTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListView d:ItemsSource="{d:SampleData ItemCount=5}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
It is just a control with a ListView. So, I want this ListView to be in any new tab that is opened.
Here is my main window with the actual tab control:
<Window x:Class="MvvmTest.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:MvvmTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="New Tab" Margin="703,6,10,401" Click="Button_Click"/>
<TabControl Name= "TabControl1" Margin="0,33,0,-33" Grid.ColumnSpan="2">
</TabControl>
</Grid>
</Window>
In this code-behind, I try to create a new tab programmatically and set the content template to the new control.
private void Button_Click(object sender, RoutedEventArgs e)
{
TabControl1.Items.Add(new TabItem() { ContentTemplate = UserControl1 });
}
This fails. I also tried setting properties in the XAML which also failed. I'm not sure what else to try.

If you're trying to use MVVM, where is your view model? The approach you have so far is not very MVVM because you're using code-behind to add tab items. The MVVM approach would be to bind the ItemSource property of the TabControl to a collection of items and let the view model add the items for you. You also cannot use a UserControl as a ContentTemplate like that without wrapping it in a DataTemplate definition.
The first thing to do is to define some view models:
// MvvmLight (from NuGet) is included for it's INotifyPropertyChanged
// (ViewModelBase) and ICommand (RelayCommand) classes. INotifyPropertyChanged
// is how Binding works between the View and the View Model. You could
// implement these interfaces yourself if you wanted to.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace MvvmTest
{
public class MainWindowViewModel : ViewModelBase
{
// store our list of tabs in an ObservableCollection
// so that the UI is notified when tabs are added/removed
public ObservableCollection<TabItemViewModel> Tabs { get; }
= new ObservableCollection<TabItemViewModel>();
// this code gets executed when the button is clicked
public ICommand NewTabCommand
=> new RelayCommand(() => Tabs.Add(new TabItemViewModel()
{ Header = $"Tab {Tabs.Count + 1}"}));
}
public class TabItemViewModel : ViewModelBase
{
// this is the title of the tab, note that the Set() method
// invokes PropertyChanged so the view knows if the
// header changes
public string Header
{
get => _header;
set => Set(ref _header, value);
}
private string _header;
// these are the items that will be shown in the list view
public ObservableCollection<string> Items { get; }
= new ObservableCollection<string>() { "One", "Two", "Three" };
}
}
Then you can fix your XAML so that it refers to the view-models that you defined. This requires defining the DataContext for your MainWindow and binding the elements of MainWindow to properties on the view model:
<Window x:Class="MvvmTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<!--Set the DataContent to be an instance of our view-model class -->
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--The Command of the button is bound to the View Model -->
<Button Grid.Row="0" HorizontalAlignment="Right" Width="100"
Content="New Tab"
Command="{Binding NewTabCommand}" />
<!--ItemsSource is bound to the 'Tabs' property on the view-
model, while DisplayMemeberPath tells TabControl
which property on each tab has the tab's name -->
<TabControl Grid.Row="1"
ItemsSource="{Binding Tabs}"
DisplayMemberPath="Header">
<!--Defining the ContentTemplate in XAML when is best.
This template defines how each 'thing' in the Tabs
collection will be presented. -->
<TabControl.ContentTemplate>
<DataTemplate>
<!--The UserControl/Grid were pointless, so I
removed them. ItemsSource of the ListView is
bound to an Items property on each object in
the Tabs collection-->
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Some column"/>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The result is that when you press the button, a new tab gets created and shown

Related

Modify textblock of a UserControl from another UserControl WPF

i have a UserControl that contains a TextBox now i am loading another UserControl that contains a TextBlock .When the button is clicked, I want to assign value entered in TextBox to TextBlock of another control that is loaded. How can i do this ?
Main UserControl
<UserControl x:Class="IntelliVentory.UserControls.CategoryControl"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="670" d:DesignWidth="1100">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Name="CategoryNameBox" Width="350" />
<Button Grid.Row="1" Click="AddCategoryFunc">Load Another Control</Button>
<Grid Grid.Row="2" Name="CategoriesWraper"></Grid>
</Grid>
</UserControl>
Another UserControl
<UserControl x:Class="IntelliVentory.UserControlModules.CategoryModule"
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:IntelliVentory.UserControlModules"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Name="CategoryName" FontSize="12" FontWeight="Thin">Category Name Here</TextBlock>
</Grid>
</UserControl>
Main UserControl.cs
Loading another UserControl.
private void AddCategoryFunc(object sender, RoutedEventArgs e)
{
UserControl categoryMod = new CategoryModule();
CategoriesWraper.Children.Add(categoryMod);
}
You want to have something like
categoryMod.CategoryNameValue = categoryControl.CategoryNameValue;
So you need to define two properties, one CategoryNameValue property with which you get the Text value of the TextBox, and one CategoryNameValue property with which you can set the Text property of the TextBlock.
Define this property in the CategoryControl class,
public string CategoryNameValue { get { return CategoryNameBox.Text; }
And this in CategoryModule class,
public string CategoryNameValue { set { CategoryName.Text = value; }
And you can start using them in your code.
You can define them as Dependency Properties instead of plain CLR properties, and then look to use data binding. With data binding both user controls can be bound to the same data model so their values are synced automatically.
Edit:
Turns out you can access a UserControl's child elements from outside as if they are public fields. That is, you can write code like this without having to define new properties
CategoryModule categoryMod = new CategoryModule();
categoryMod.CategoryName.Text = CategoryNameBox.Text;
CategoriesWraper.Children.Add(categoryMod);

Why does Treeview if MVVM model inherit expanded across all tabs?

I do not understand why when I toggle the expanding of a tab's treeview in WPF, that it then affects the expanding of all tab's treeviews. I want each tab's treeview to be independent from one another. It's a very simple MVVM setup with a few classes.
Here are the files from the project
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:WpfApplication1"
xmlns:view="clr-namespace:WpfApplication1.View"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="250">
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
<TabControl x:Name="tabControl1" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<view:TabItemView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
TabItemView.xaml
<UserControl x:Class="WpfApplication1.View.TabItemView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Content}" />
<TreeView Grid.Row="1" Background="Transparent">
<TreeViewItem Header="Favorites">
<TreeViewItem Header="USA"></TreeViewItem>
<TreeViewItem Header="Canada"></TreeViewItem>
<TreeViewItem Header="Mexico"></TreeViewItem>
</TreeViewItem>
</TreeView>
</Grid>
</UserControl>
ViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public class ViewModel
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = DateTime.Now.ToString("Tab 1"), Content = DateTime.Now.ToString("F") });
TabItems.Add(new TabItem { Header = DateTime.Now.ToString("Tab 2"), Content = DateTime.Now.ToString("F") });
TabItems.Add(new TabItem { Header = DateTime.Now.ToString("Tab 3"), Content = DateTime.Now.ToString("F") });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
}
The TabControl reuses the same content element for each item. All it does is change the bound data context (i.e. your view model), updating the elements within the templated element that are bound to the view model. So the other state of the view remains the same as you switch from one tab to the next.
It is possible to force a new content element to be created each time you change the tab; one way of doing this is to declare the content element's template as a resource, add x:Shared="False" to the resource declaration, and then use the resource as the value for a Setter applied in a Style targeting the TabItem type.
Going through the setter to apply the template to each TabItem is required — and by TabItem here I mean the WPF TabItem, not your view model class, the name of which I'd change, to avoid confusion, if I were you. Using x:Shared won't help if you just set the TabControl.ContentTemplate directly once.
For example:
<TabControl.Resources>
<DataTemplate x:Key="tabItemTemplate" x:Shared="False">
<l:TabItemView />
</DataTemplate>
<s:Style TargetType="TabItem">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=tabItemTemplate}"/>
</s:Style>
</TabControl.Resources>
However, this has the opposite effect: rather than keeping the state for each item as you switch from tab to tab, the view state is reset entirely, because a whole new content element is created every time you switch.
If this is acceptable to you, then that will work fine. If however you are looking for each tab to retain whatever configuration it was in when the user last viewed that tab, you will have to preserve that state yourself. For example, you could add a bool property to your view model to remember the current setting for each item and bind that to the IsExpanded property for the top-level TreeViewItem:
View model:
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
public bool IsExpanded { get; set; }
}
View:
<UserControl x:Class="TestSO33125188TreeViewTemplate.TabItemView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Content}" />
<TreeView Grid.Row="1" Background="Transparent">
<TreeViewItem Header="Favorites" IsExpanded="{Binding IsExpanded, Mode=TwoWay}">
<TreeViewItem Header="USA"></TreeViewItem>
<TreeViewItem Header="Canada"></TreeViewItem>
<TreeViewItem Header="Mexico"></TreeViewItem>
</TreeViewItem>
</TreeView>
</Grid>
</UserControl>
Note that for this to work, you need to explicitly set the binding Mode property to TwoWay, as the default is OneWay and won't otherwise copy the current TreeViewItem.IsExpanded value back to the view model.
Note also that for your specific example, you can get away with a simple container view model, without implementing INotifyPropertyChanged or similar mechanism. WPF is forced to copy the updated property value back to the view whenever the current tab is changed. But personally, I would go ahead and add the INotifyPropertyChanged implementation; it would be too easy to find yourself reusing the technique in a different scenario where WPF doesn't automatically detect that you've updated the property value, causing a frustrating bug that takes a while to track down and fix.

How to access this user control in Main Window

I have one user control in my WPF app
<UserControl x:Class="NewWPFApp.ProgressControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Expander Header="{Binding Path=Headerval}">
<StackPanel Margin="10,4,0,0">
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=records}"/>
</StackPanel>
</Expander>
</Grid>
and in my Mainwindow when I am doing this
<Window xmlns:NewWPFApp="clr-namespace:NewWPFApp" x:Class="NewWPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox x:Name="peopleListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="AliceBlue">
<NewWPFApp:ProgressControl Height="100" Width="100"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I cant see the output.
If I remove it from the Data template then it works.But not inside the data template.
What am I missing ???
Thanks
ListBox.ItemTemplate specifies how items added to a list box will be displayed. In your case, you uses your own custom control. However, at the same time you added no items to the list box so there is nothing to display. To populate the list box you can use binding e.g.:
<ListBox ItemsSource="{Binding ItemsToBeDisplayed}" x:Name="peopleListBox" >
...
</ListBox>
Where ItemsToBeDisplayed is a property of a view model (if you use MVVM pattern) that returns a collection of objects. If not you can populate ItemsSource without using binding:
var list = new List<People>();
//Add objects to a list
peopleListBox.ItemsSource = list;
I assumed that you have People class that models people you want to display in your application. People class should have Headerval and records properties because you use them in your your ProgressControl control. If you do as described, a new instance of your ProgressControl control will be created for every object in ItemsSource.

WPF Binding inner control with parent data context

I made a user control
<UserControl x:Class="MyApp.MyControl"
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"
mc:Ignorable="d" x:Name="uc">
<Grid Width="Auto" Height="Auto">
<TextBlock Text="{Binding Path=DataContext.TextContent, ElementName=uc}"/>
<TextBlock Text="{Binding Path=DataContext.TextContent2, ElementName=uc}"/>
</Grid>
I want the sub-controls in the defined control(uc) will bind to the properties of uc.DataContext. I used the defined control as follows:
<Window x:Class="Tms.TMSClient.Views.MainWindow" Name="window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:control="clr-namespace:MyApp"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary">
<control:MyControl DataContext="{Binding Path=MyControlVM}"/>
The DataContext which is assigned to the window has this structure: WindowVM.MyControlVM.TextContent.
The given code does not work because the textbox's DataContext is bound to WindowVM instead. I think the problem may be because the inner textbox is bound before the defined control (uc) is, thus the bounded DataContext for uc does not take effect yet.
What I want is: the custom control (MyControl) will be bound to its corresponding viewmodel (MyControlVM), and the inner elements of MyControl will be bound to the properties of MyControlVM.
Do you have any solutions for this problem?
If I understand you correctly, you want to data bind a property from your MyControl view model to a TextBox.Text property inside the MyControl UserControl. If that is correct, then you can use a RelativeSource Binding, or the ElementName syntax that you are already using.
First, make sure that your view model is set as the DataContext for the UserControl:
public MyControl()
{
DataContext = new YourControlViewModel();
}
As child controls automatically inherit their parent's DataContext objects, you can now reference this view model from the TextBox through the MyControl.DataContext property from the UserControl's XAML:
<TextBlock Text="{Binding DataContext.TextContent,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
That's all you need.
<TextBlock Text="{Binding Path=TextContent}"/>
works for me in my test-application.
MainWindow.xaml
<Window x:Class="DataContextTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:DataContextTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<my:MyOuterDataContext />
</Window.DataContext>
<Grid>
<my:MyControl DataContext="{Binding Path=MyInnerDataContext}" />
</Grid>
MyControl.xaml
<UserControl x:Class="DataContextTest.MyControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Path=TextContent}" />
</Grid>
DataContexts:
public class MyOuterDataContext
{
public MyInnerDataContext MyInnerDataContext { get; set; }
public MyOuterDataContext()
{
MyInnerDataContext = new MyInnerDataContext();
}
}
public class MyInnerDataContext
{
public string TextContent { get { return "foo"; } }
}
By default every control inherits its DataContext from its parent control. Thus there is no need to explicitly bind to it.
Indeed, when you want to bind a control's DataContext to a nested property then you have to specifiy this:
<control:MyControl DataContext="{Binding Path=TextContent}"/>

Send commands between two usercontrols in WPF

I'm trying to send a command from one UserControl to another. The first one contains a button and the second one contains a custom class that derives from the Border class.
I want when I click the Button in UserControl to execute the Redraw method in CustomBorder in UserControl2.
Here is what I have done so far.
MainWindow.xaml:
<Window x:Class="SendCommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<sendCommands:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0"/>
<sendCommands:UserControl2 Grid.Row="1"/>
</Grid>
</Window>
UserControl1:
<UserControl x:Class="SendCommands.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Redraw"
Width="200"
Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
UserControl2:
<UserControl x:Class="SendCommands.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands">
<Grid>
<sendCommands:CustomBorder Background="Black">
</sendCommands:CustomBorder>
</Grid>
</UserControl>
CustomBorder class:
using System.Windows;
using System.Windows.Controls;
namespace SendCommands
{
public class CustomBorder : Border
{
public void Redraw()
{
// Operations to redraw some elements inside the CustomBorder
MessageBox.Show("We did it!");
}
}
}
ViewModel.cs:
namespace SendCommands
{
class ViewModel
{
}
}
Please somebody help me learn this once and for all. I'm new to MVVM concept and I have read a lot but no results. I really need a practical solution to get the concepts right.
What does your Redraw method is really supposed to do? Change border if some property has changed? E.g. an item in a shop was sold out?
In general view should reflect changes in the ViewModel. This happens automatically with bindings. View element, such as button, can communicate with ViewModel with Commands.
Therefore your button would look like this:
<Button Command={Binding ClickCommand} />
In your ViewModel you'll have a
public DelegateCommand ClickCommand {get; private set;}
and
ClickCommand = new DelegateCommand(ExecuteClick);
ExecuteClick would update some properties in the view model, e.g. if you have an online shop, set a SoldOut property of bike object to true.
Your view will in turn bind to properties of Bike and change its appearance if some properties change. Changes like text will happen by themselves, more complicated changes can be achieved with converters (e.g. change bckaground to red in SoldOut is true):
<Resources>
<SoldOutToBckgrConverter x:Key="soldOutToBckgrConverter" />
</Resources>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
SoldOutToBckgrConverter implements IValueConverter and converts True to Red.
Note: SelectedItem is again bound to a list, whose source is bound to sth like ObservableCollection on your ViewModel.
So basically you shouldn't call redraw, it should all redraw itself automatically with commands, changes in VM and bindings.
Update to your comment: that's what I tried to show, given that I understood the purpose of your redraw right. In my example with products and red background for sold items this will look like this:
In your VM:
public ObservableCollection<MyProduct> Products {get;set;}
private MyProduct selectedProduct;
public MyProduct SelectedProduct
{
get {return selectedProduct;}
set {
if (selectedProduct != value) {
selectedProducat = value;
RaisePropertyChanged(()=>SelectedProduct;
}
}
}
MyProduct has Model property (real world product model, i.e. brand) and SoldOut.
In your View:
<ListBox SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" ItemsSource="{Binding Products}" >
<ListBox.ItemTemplate>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
</ListBox.ItemTemplate>
</ListBox>
Now when you click you button, VM changes SelectedProduct and Binding cahnges background (or border..)
You can use "CallMethodAction" behavior provided by Expression Blend. Add System.Windows.Interactivity.dll to your project and you can bind the method to event. In your case "ReDraw" method must bound to "Click" event. More information on this behavior.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RefreshButtonClick">
<ei:CallMethodAction MethodName="RedrawCustomBorder"
TargetObject="{Binding ElementName=customBorder}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</sendCommands:UserControl1>
<sendCommands:UserControl2 Grid.Row="1" x:Name="customBorder"/>
</Grid>

Categories

Resources