I have a ParentViewModel which has ObservableCollection named Notices.
public class Notice
{
public string Content { get; set; }
public NoticeType Type { get; set; }
}
I have static controls at user control where I want to bind this observablecollection to this static controls.
And I don't know how binding this Notices depending of its Types.
I want bind notice with Type FirstType to "first row" and notice with Type SecondType to "second row"
Also If user check checkbox Notice should be removed from collection.
There are my codes
<UserControl x:Class="Repo.UserControlNotices"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid DataContext="{Binding}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Text="FirtNotice" >
</TextBox>
<StackPanel Grid.Row="0" Grid.Column="1">
<TextBox Text="{Binding Path =Content}" >
</TextBox>
<CheckBox >
</CheckBox>
</StackPanel>
<TextBox Grid.Row="1" Text="SecondNotice">
</TextBox>
<StackPanel Grid.Row="1" Grid.Column="1">
<TextBox Text="{Binding Path =Content}" >
</TextBox>
<CheckBox >
</CheckBox>
</StackPanel>
</Grid>
</UserControl>
class ParentViewModel
{
public ObservableCollection<Notice> Notices { get; set; }
public ParentViewModel()
{
ObservableCollection<Notice> loadedNotices = new ObservableCollection<Notice>();
loadedNotices.Add(new Notice() { Content = "Something", Type = NoticeType.FirstType });
loadedNotices.Add(new Notice() { Content = "Something", Type = NoticeType.SecondType });
Notices = loadedNotices;
}
}
<Window x:Class="Repo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Repo:ParentViewModel x:Key="parentVM"/>
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="parentVM"/>
</Window.DataContext>
<Grid>
<Repo:UserControlNotices DataContext="{Binding Path=Notices}">
</Repo:UserControlNotices>
</Grid>
</Window>
This is example in winforms:
I'm not exactly sure what your asking but I think you most likely need to use the IValueConverter interface. You declare a new class implementing the IValueConverter interface and implement the Convert and ConvertBack methods like so
public Converter implements IValueConverter{
Object Convert(value, targetType, parameter, culture){
// 'value' has the bound value from the xaml
// do some converting and return the result
}
Object Convert(value, targetType, parameter, culture){
// you can figure this one out, usually used for a two-way relationship
}
}
In the xaml, you need a ResourceDictionary in your xaml with something like
xmlns:y="clr-namespace:NamespaceWithClass">
<y:Converter x:Key="someName" />
And reference this converter in your controls
<TextBox Text="{Binding Content, Converter={StaticResource someName}}" >
</TextBox>
Not sure if this is exactly what your looking for but I hope it helps.
Is it not easier to just have two properties on ParentViewModel, e.g.,...
public static IEnumerable<Notice> FirstTypeNotices
{
get
{
return Notices.Where(n => n.Type == NoticeType.FirstType);
}
}
...and bind to those?
You'll probably have to hook up to the Notices.CollectionChanged event and raise PropertyChanged events for each of the two properties.
Related
I'm new to WPF and I'm having trouble binding text to a user control I made. It's a simple control, it's just basically a button with text and a image that I want to reuse in several Views.
Here is my User Control's .cs
public partial class MenuItemUserControl : UserControl
{
public string TextToDisplay { get; set; }
public MenuItemUserControl()
{
InitializeComponent();
DataContext = this;
}
}
Here is my User Control's xaml
<UserControl x:Class="Class.Controls.MenuItemUserControl"
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:Class.Controls"
mc:Ignorable="d"
d:DesignHeight="83.33" d:DesignWidth="512">
<Grid>
<Button Style="{StaticResource MenuItemStyle}" Height="Auto" Width="Auto">
<DockPanel LastChildFill="True" Width="512" Height="83.33" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Resources/MenuArrow.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock d:Text="sample" Text="{Binding TextToDisplay, Mode=OneWay}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="{StaticResource MenuItems}"/>
</DockPanel>
</Button>
</Grid>
</UserControl>
Here is my View xaml
<Page x:Class="Class.Views.MenuOperate"
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:uc="clr-namespace:Class.Controls"
xmlns:local="clr-namespace:Class.Views"
xmlns:properties="clr-namespace:Class.Properties"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:viewmodels="clr-namespace:Class.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MenuOperateViewModel}"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="1024"
Title="MenuOperate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="512"/>
<ColumnDefinition Width="512"/>
</Grid.ColumnDefinitions>
<uc:MenuItemUserControl TextToDisplay="{Binding StartStop, Mode=TwoWay}" Grid.Row="0" Grid.Column="0"/>
</Grid>
</Page>
Here is my View Model .cs
namespace Class.ViewModels
{
public class MenuOperateViewModel : ObservableObject
{
private string? _StartStop;
public MenuOperateViewModel()
{
StartStop = Properties.Resources.MenuOperateStart;
}
public string? StartStop
{
get => _StartStop;
set => SetProperty(ref _StartStop, value);
}
}
}
This is the error I get in my View Xaml:
Object of Type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'.
There are two things that prevent that the expression
TextToDisplay="{Binding StartStop}"
works.
The target property of the Binding, i.e. TextToDisplay must be a dependency property.
You must not explicity set the UserControl's DataContext. The Binding will resolve the source property path relative to the current DataContext, i.e. with DataContext = this; in the control's constructor, it expects the source property StartStop on the UserControl, which is obviously wrong.
For details, see Data binding overview
Your code should look like this:
public partial class MenuItemUserControl : UserControl
{
public static readonly DependencyProperty TextToDisplayProperty =
DependencyProperty.Register(
nameof(TextToDisplay),
typeof(string),
typeof(MenuItemUserControl));
public string TextToDisplay
{
get { return (string)GetValue(TextToDisplayProperty); }
set { SetValue(TextToDisplayProperty, value); }
}
public MenuItemUserControl()
{
InitializeComponent();
}
}
The Binding in the UserControl's XAML would then use a RelativeSource Binding.
<TextBlock Text="{Binding TextToDisplay,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
I have following code:
MainWindow.xaml
<Window x:Class="SampleApplication.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"
DataContext="{Binding Employee}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID:"/>
<Label Grid.Row="1" Grid.Column="0" Content="Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding EmpName}" />
</Grid>
</Window>
Employee.cs
namespace SampleApplication
{
public class Employee
{
public Employee()
{
EmployeeDetails employeeDetails = new EmployeeDetails();
employeeDetails.EmpID = 123;
employeeDetails.EmpName = "ABC";
}
}
public class EmployeeDetails
{
private int empID;
public int EmpID
{
get
{
return empID;
}
set
{
empID = value;
}
}
private string empName;
public string EmpName
{
get
{
return empName;
}
set
{
empName = value;
}
}
}
}
This is very simple code and I just want to bind the EmpID and EmpName properties in my Employee.cs class to Text properties of Textboxes in MainWindow.xaml but nothing is appearing in my these textboxes when I am running the code. Is the binding right?
This code will always fail.
As written, it says: "Look for a property named "Employee" on my DataContext property, and set it to the DataContext property". Clearly that isn't right.
To get your code to work, as is, change your window declaration to:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Employee/>
</Window.DataContext>
This declares a new XAML namespace (local) and sets the DataContext to an instance of the Employee class. This will cause your bindings to display the default data (from your constructor).
However, it is highly unlikely this is actually what you want. Instead, you should have a new class (call it MainViewModel) with an Employee property that you then bind to, like this:
public class MainViewModel
{
public Employee MyEmployee { get; set; } //In reality this should utilize INotifyPropertyChanged!
}
Now your XAML becomes:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
...
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding MyEmployee.EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding MyEmployee.EmpName}" />
Now you can add other properties (of other types, names), etc. For more information, see Implementing the Model-View-ViewModel Pattern
First of all you should create property with employee details in the Employee class:
public class Employee
{
public Employee()
{
EmployeeDetails = new EmployeeDetails();
EmployeeDetails.EmpID = 123;
EmployeeDetails.EmpName = "ABC";
}
public EmployeeDetails EmployeeDetails { get; set; }
}
If you don't do that, you will create instance of object in Employee constructor and you lose reference to it.
In the XAML you should create instance of Employee class, and after that you can assign it to DataContext.
Your XAML should look like this:
<Window x:Class="SampleApplication.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"
xmlns:local="clr-namespace:SampleApplication"
>
<Window.Resources>
<local:Employee x:Key="Employee" />
</Window.Resources>
<Grid DataContext="{StaticResource Employee}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID:"/>
<Label Grid.Row="1" Grid.Column="0" Content="Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding EmployeeDetails.EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding EmployeeDetails.EmpName}" />
</Grid>
</Window>
Now, after you created property with employee details you should binding by using this property:
Text="{Binding EmployeeDetails.EmpID}"
There are several issues here.
You can't assign DataContext as DataContext="{Binding Employee}" because it's a complex object which can't be assigned as string. So you have to use <Window.DataContext></Window.DataContext> syntax.
You assign the class that represents the data context object to the view, not an individual property so {Binding Employee} is invalid here, you just have to specify an object.
Now when you assign data context using valid syntax like below
<Window.DataContext>
<local:Employee/>
</Window.DataContext>
know that you are creating a new instance of the Employee class and assigning it as the data context object. You may well have nothing in default constructor so nothing will show up. But then how do you manage it in code behind file? You have typecast the DataContext.
private void my_button_Click(object sender, RoutedEventArgs e)
{
Employee e = (Employee) DataContext;
}
A second way is to assign the data context in the code behind file itself. The advantage then is your code behind file already knows it and can work with it.
public partial class MainWindow : Window
{
Employee employee = new Employee();
public MainWindow()
{
InitializeComponent();
DataContext = employee;
}
}
I'm trying to create a DataTemplate for a View, to show a specific UserControl type (like a texbox, combobox, custom control or another View) based on the type of object it is bound to.
I have the following MVVM framework:
FieldView is tied to an instance of FieldPresenter, and should display a <Textblock /> for the "Label" property, and a UserControl or another View for the Value (based on the Type of the value), with it's DataSource set to the Value property of the Presenter. Currently, I do not have the second part working. I can't figure out how to write a WPF template for what I need.
ViewModel:
public class FieldPresenter : Observable<object>, IFieldPresenter, INotifyPropertyChanged
{
public FieldPresenter() { }
public FieldPresenter(object value)
{
Value = value;
}
object IFieldPresenter.Value
{
get
{
return base.Value;
}
set
{
base.Value = value;
OnPropertyChanged("Value");
}
}
private string _label;
public virtual string Label
{
get
{
return _label;
}
private set
{
_label = value;
OnPropertyChanged("Label");
}
}
}
View:
<UserControl x:Class="My.Views.FieldView"
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:ViewModels="clr-namespace:My.ViewModels"
mc:Ignorable="d"
d:DesignHeight="24" d:DesignWidth="100">
<UserControl.DataContext>
<ViewModels:FieldPresenter/>
</UserControl.DataContext>
<UserControl.Template>
<ControlTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,0,0" HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth}">
<TextBlock Text="{Binding Label}" FontWeight="Bold" Height="32" HorizontalAlignment="Stretch"/>
<TextBox Text="{Binding Value}" Height="Auto" HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
I'm curious if what I'm trying to do is even possible, or if I can workaround it by making my Presenter viewmodel return a UserControl rather than an object value, and have the Presenter parse the UserControl Type from the object type, but I don't feel like my Presenter should be instantiating Controls (or what is technically an unbound view). Should I make an interface, something like IViewAs<controlType> { controlType View { get; } }?
How else would I replace <TextBox Text="{Binding Value}" /> in the above script with some kind of template of a UserControl based on the databound object's type?
You almost certainly want a ContentTemplateSelector :
Code:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Primitive primitive;
primitive = new Sphere();
// primitive = new Cube();
DataContext = primitive;
}
}
internal abstract class Primitive
{
public abstract string Description { get; }
}
internal class Cube : Primitive
{
public override string Description
{
get { return "Cube"; }
}
}
internal class Sphere : Primitive
{
public override string Description
{
get { return "Sphere"; }
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
if (frameworkElement != null && item != null)
{
if (item is Cube)
{
return frameworkElement.FindResource("CubeTemplate") as DataTemplate;
}
if (item is Sphere)
{
return frameworkElement.FindResource("SphereTemplate") as DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
}
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<local:MyTemplateSelector x:Key="myTemplateSelector" />
<DataTemplate x:Key="CubeTemplate" DataType="local:Cube">
<Border BorderBrush="Blue"
BorderThickness="1"
CornerRadius="5" />
</DataTemplate>
<DataTemplate x:Key="SphereTemplate" DataType="local:Sphere">
<Border BorderBrush="Red"
BorderThickness="1"
CornerRadius="50" />
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="{Binding Description}"
d:DataContext="{d:DesignInstance local:Primitive}" />
<ContentControl Grid.Row="1"
Content="{Binding}"
ContentTemplateSelector="{StaticResource myTemplateSelector}" />
</Grid>
</Window>
Result:
See the documentation for more:
https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx
I've got a complex question I've been working on for a good week now trying to work correctly. I am using WPF .NET 4.5, the MVVM pattern, and Prism. I want to display a TreeView that binds to an ObservableCollection<IScript> LoadedScripts:
namespace Library.Data.Scripting
{
public interface IScript : INotifyPropertyChanged
{
...
IScriptDescription ScriptDescription { get; }
...
}
public interface IScriptDescription : INotifyPropertyChanged
{
...
string Name { get; }
IEnumerable<ISectionDescription> Sections { get; }
ScriptStatus Status { get; }
...
}
public interface ISectionDescription : INotifyPropertyChanged
{
...
string Name { get; }
ScriptStatus Status { get; }
...
}
}
I want the TreeView to display the list of scripts and each script to have children for the script's sections. For each node that is visible, I want to show the Name and Status. That is, for each IScript in LoadedScripts show a textbox of ScriptDescription.Name and ScriptDescription.Status. For each child ISectionDescription, a TextBox containing ISectionDescription.Name and ISectionDescription.Status.
This is all fairly straightforward, except for the fact that I'm binding to Interfaces. I found how I'm supposed to be able to overcome this, using a DataTemplateSelector, but my treeview isn't displaying the text correctly in my templates. Instead, I am getting the object.ToString() for the IScripts in my ObservableCollection<IScript> LoadedScripts with no children. I'm not getting any kind of errors or warnings in the Output window either.
I feel like I am really close but I can't get it to correctly display my data. I have verified I am indeed getting my data and if I put a basic DataTemplate for TreeView.ItemTemplate I am able to display the top-level IScript nodes. I'm not sure what I'm doing wrong, any input is much appreciated!
Here's my DataTemplateSelector:
namespace Library.App.GUI
{
public class ScriptDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is IScript)
{
return element.FindResource("ScriptTemplate") as HierarchicalDataTemplate;
}
else if (element != null && item != null && item is ISectionDescription)
{
return element.FindResource("SectionTemplate") as DataTemplate;
}
return null;
}
}
}
The ViewModel:
namespace Library.App.GUI
{
public class TestStatusViewModel
{
private IScriptManager ScriptManager;
public ObservableCollection<IScript> LoadedScripts
{
get
{
return ScriptManager.LoadedScripts;
}
}
public TestStatusViewModel()
{
//Locate the ScriptManager in the MEF container using Microsoft.Practices.ServiceLocation.ServiceLocator
ScriptManager = ServiceLocator.Current.GetInstance<IScriptManager>();
}
}
}
Here's my XAML:
<UserControl x:Class="Library.App.GUI.TestStatusView"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:Library.App.GUI"
xmlns:script="clr-namespace:Library.Data.Scripting;assembly=MTFCommon"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism;assembly=Microsoft.Practices.Prism"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="200">
<UserControl.Resources>
<local:ScriptDataTemplateSelector x:Key="ScriptTemplateSelector"/>
<HierarchicalDataTemplate x:Key="ScriptTemplate" ItemsSource="{Binding ScriptDescription.Sections}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" Text="{Binding ScriptDescription.Name}" />
<TextBlock Grid.Column="1" TextAlignment="Right" FontWeight="Bold" HorizontalAlignment="Stretch" Text="{Binding ScriptDescription.Status}" />
</Grid>
</HierarchicalDataTemplate>
<DataTemplate x:Key="SectionTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" Text="{Binding Name}" />
<TextBlock Grid.Column="1" TextAlignment="Right" FontWeight="Bold" HorizontalAlignment="Stretch" Text="{Binding Status}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ItemsControl HorizontalAlignment="Stretch">
<TreeView Name="treeView" ItemTemplateSelector="{StaticResource ScriptTemplateSelector}" ItemsSource="{Binding LoadedScripts}" />
</ItemsControl>
</UserControl>
P.S.: Everything in the Library.Data namespace I am not able to personally edit since it is an external library. I can put in a request to have it changed if that must happen, but prefer not.
P.P.S.: This is my first SO question, please let me know if I missed any important information. Thanks!
If your DataTemplateSelector not working, you might want to check to see if element.FindResource is returning anything. If it returns null (or your as returns null), then the ToString will be used to display the items in the tree.
I'm currently developing in WPF (Surface 2.0) and using the MVVM pattern for most parts of my application. I am, unfortunately, currently facing a rather complicated issue I hope you guys can assist me on:
I have a View and a ViewModel that belongs to it. The View contains a two-way binding to a property in the ViewModel:
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
While the View is first loaded, the setter of SelectedPivotItemIndex is called. This is fine, except that the setter is called before the rest of the view loaded. Since the setter sends messages (via MVVMLight's Messenger) to other viewmodels that are created later in the view, this is a problem - those messages never reach their destination since no receiver is registered for them so far.
public int SelectedPivotItemIndex
{
get
{
return this.selectedPivotItemIndex;
}
set
{
if (value != this.selectedPivotItemIndex)
{
this.selectedPivotItemIndex = value;
this.ReportPropertyChanged("SelectedPivotItemIndex");
(...)
ChangeSomeOtherViewModelProperty msg = new ChangeSomeOtherViewModelProperty { Property = newValueCalculatedBefore };
Messenger.Default.Send<ChangeSomeOtherViewModelProperty>(msg);
}
}
}
The only solution I can think of right now, would be to create a LoadedEventHandler in the ViewModel and call the SelectedPivotItemIndex setter again. I don't really like that, though:
For once, the setter runs again (which creates a rather large collection that is passed to the message). Don't know if it would really impact performance, but still seems unnecessary.
Secondly, it just seems kind of hackish and error prone to me, since every property has to be initialized manually in the loaded event.
Is there any solution to this problem better than just manually calling the setter?
i dont have a tutorial for viewmodel first, but i'm sure the are a lot examples out there. viewmodel first is nothing more then you have the viewmodel instance first and then let wpf create the view(via datatemplate).
let say your mainview should show a view with your PivotBarEntries. so what you do now is to create a pivotbarviewmodel in your mainviewmodel (DI, MEF, new() what ever). your mainviewmodel expose the pivotvw as a property and bind it to a ContentPresenter.Content in your mainview. at least you have to create a DataTemplate for your pivotvw DataType.
<DataTemplate DataType="{x:Type local:PivotViewModel>
<view:MyPivotView/>
</DataTemplate>
thats about viewmodel first, you do not rely on load events on view anymore, because your vm is created first.
of course for your specific problem you just have to be sure that all your components(VM's) which listen to your messenger should be created
your xaml
<ContentPresenter Content="{Binding MyPivotDataVM}" />
<ContentPresenter Content="{Binding MySomeOtherStuffVM}" />
instead of view first
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
EDIT: very simple example for viewmodel first. ps: i use DI with MEF to create my object path.
app.xaml
<Application x:Class="WpfViewModelFirst.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfViewModelFirst="clr-namespace:WpfViewModelFirst">
<!--StartUp Uri is removed-->
<Application.Resources>
<!--comment these datatemplates and see what happens-->
<DataTemplate DataType="{x:Type WpfViewModelFirst:PivotViewModel}">
<WpfViewModelFirst:PivotView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherViewModel}">
<WpfViewModelFirst:OtherView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherChildViewModel}">
<WpfViewModelFirst:OtherChildView/>
</DataTemplate>
</Application.Resources>
</Application>
app.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//to be fair, sometimes i create the ApplicationRoot(JUST MainWindow with view first, and just the rest with viewmodel first.)
var mainvm = new MainViewModel();
var mainview = new MainWindow {DataContext = mainvm};
this.MainWindow = mainview;
this.MainWindow.Show();
}
}
mainview.xaml
<Window x:Class="WpfViewModelFirst.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>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.ColumnSpan="2" Grid.Row="0"/>
<ContentPresenter Content="{Binding MyPivot}" Grid.Row="1" Grid.Column="0" />
<ContentPresenter Content="{Binding MyOther}" Grid.Row="1" Grid.Column="1" />
</Grid>
</Window>
mainviewmodel.cs
public class MainViewModel
{
public string MyProp { get; set; }
public PivotViewModel MyPivot { get; set; }
public OtherViewModel MyOther { get; set; }
public MainViewModel()
{
this.MyProp = "Main VM";
this.MyPivot = new PivotViewModel();
this.MyOther = new OtherViewModel();
}
}
PivotViewmodel
public class PivotViewModel
{
public string MyProp { get; set; }
public ObservableCollection<string> MyList { get; set; }
public PivotViewModel()//Dependency here with constructor injection
{
this.MyProp = "Test";
this.MyList = new ObservableCollection<string>(){"Test1", "Test2"};
}
}
OtherViewmodel
public class OtherViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel MyChild { get; set; }
public OtherViewModel()
{
this.MyProp = "Other Viewmodel here";
this.MyChild = new OtherChildViewModel();
}
}
OtherChildViewmodel
public class OtherChildViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel()//Dependency here with constructor injection
{
this.MyProp = "Other Child Viewmodel";
}
}
PivotView
<UserControl x:Class="WpfViewModelFirst.PivotView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0"/>
<ListBox ItemsSource="{Binding MyList}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherView
<UserControl x:Class="WpfViewModelFirst.OtherView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0" />
<ContentPresenter Content="{Binding MyChild}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherChildView
<UserControl x:Class="WpfViewModelFirst.OtherChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="{Binding MyProp}" />
</Grid>
</UserControl>