Problem with design time data in WPF - wpf

Hi I try use my first design time data in wpf. I use tutorial from:
http://karlshifflett.wordpress.com/2009/10/21/visual-studio-2010-beta2-sample-data-project-templates/
http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/
I create simple data class, here is it:
public class Avatar:INotifyPropertyChanged
{
private string _name;
private string _surname;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
public string Surname
{
get { return _surname; }
set
{
_surname = value;
NotifyPropertyChanged("Surname");
}
}
public new event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Then I created sample data:
<TestForDesignTimeData:Avatar xmlns:TestForDesignTimeData="clr-namespace:TestForDesignTimeData" Name="John" Surname="Smith"/>
And try use design time data in wpf window:
<Window x:Class="TestForDesignTimeData.MainWindow"
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:TestForDesignTimeData="clr-namespace:TestForDesignTimeData"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel d:DataContext="{d:DesignInstance TestForDesignTimeData:Avatar}">
<TextBlock Background="Yellow" Height="40" Width="250" Text="{Binding Path=Name}"/>
<TextBlock Background="Yellow" Height="40" Width="250" Text="{Binding Path=Surname}"/>
</StackPanel>
</Window>
But I see in designer empty textboxes. What I do bad?

You need to create a derived class from Avatar that will be used in the design time and define the sample data in the constructor of this class:
public class AvatarDesignTime : Avatar
{
public AvatarDesignTime()
{
Name = "John";
Surname = "Smith";
}
}
Then you need to specify IsDesignTimeCreatable=True for the DesignInstance to enable instance creation in the design time (otherwise the type that you specify is used just for information about type members in order to setup bindings in the design time):
<StackPanel d:DataContext="{d:DesignInstance TestForDesignTimeData:AvatarDesignTime, IsDesignTimeCreatable=True}">

Did this often for Windows Phone projects but never had to derive a class for design time data when using the MVVM pattern.
View:
<phone:PhoneApplicationPage
d:DataContext="{d:DesignData ../DesignData/AvatarSampleData.xaml}" .../>
Design Time Data (AvatarSampleData.xaml):
<local:AvatarViewModel
xmlns="http:schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http:schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNameSpace.ViewModels"
Name="Harald"
Surname="Flasch">
</local:AvatarViewModel>
hth,
hfrmobile

Related

Progerssbar won't update

I'm trying to implement a progressbar in my WPF application.
So I added one to my view
<ProgressBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Height="31"
Minimum="0"
Maximum="50"
Value="{Binding CurrentProgress}" />
My ViewModel got a new property:
public int CurrentProgress
{
get { return mCurrentProgress; }
set
{
if (mCurrentProgress != value)
{
mCurrentProgress = value;
RaisePropertyChanged("CurrentProgress");
}
}
}
When my load command executes, it raises an Generated event for every file loaded.
And the EventHandler for this event adds +1 to the 'CurrentProgress' property like this:
private void GeneratedHandler(object sender, EventArgs eventArgs)
{
CurrentProgress++;
}
But I don't see any progress on the bar. Does anybody see what I'm doing wrong?
Thanks in advance!
I've tried reproducing your problem, but it worked just fine here.
Anyway, there are a few steps that you can follow:
Make sure that you are not loading your files on the UI thread. If you are, take a look at "Showing progress while performing a lengthy task" on this article.
Make sure the DataContext of your Window is correct, your ViewModel implements System.ComponentModel.INotifyPropertyChanged and your RaisePropertyChanged method is correct.
Here's the code I've used (do not copy and paste the app.xml):
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string property = "")
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private int _Progress;
public int Progress
{
get
{
return _Progress;
}
set
{
if(value != Progress)
{
_Progress = value;
NotifyPropertyChanged();
}
}
}
}
MainWindow.xml
<Window x:Class="WpfApplication1.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="{StaticResource ResourceKey=ViewModel_MainWindow}">
<Grid>
<ProgressBar Value="{Binding Progress}" Minimum="0" Maximum="50" />
</Grid>
And the app.xaml:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:local="clr-namespace:WpfApplication1" > <!--change the namespace to the one where you ViewModel is-->
<Application.Resources>
<local:MainWindowViewModel x:Key="ViewModel_MainWindow" /> <!--important-->
</Application.Resources>

How to have design time support for nested view models in Caliburn Micro?

Using VS2013 and Caliburn.Micro 2.0.2
Given this example of:
a shell view model that has a nested view model property, and
both the shell and nested view models having a Name property:
It seems that during design time the nested view model property is ignored. Is there a way to support this?
public class NestedViewModel : PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
public NestedViewModel()
{
Name = "Nested";
}
}
<UserControl
x:Class="WpfApplication1.Views.NestedView"
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:cal="http://www.caliburnproject.org"
xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:NestedViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True">
<Grid>
<Label x:Name="Name" FontSize="16" Background="LightGreen"/>
</Grid>
</UserControl>
The green label shows the correct Name for the nested view model in the designer:
public class ShellViewModel : PropertyChangedBase
{
private string _name;
private NestedViewModel _nestedViewModel;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
public NestedViewModel NestedViewModel
{
get { return _nestedViewModel; }
set
{
if (Equals(value, _nestedViewModel)) return;
_nestedViewModel = value;
NotifyOfPropertyChange(() => NestedViewModel);
}
}
public ShellViewModel()
{
NestedViewModel = new NestedViewModel();
Name = "Shell";
}
}
<UserControl
x:Class="WpfApplication1.Views.ShellView"
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:viewModels="clr-namespace:WpfApplication1.ViewModels"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:ShellViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" x:Name="NestedViewModel"/>
<Label Grid.Row="1" x:Name="Name" FontSize="16" Background="RoyalBlue"/>
</Grid>
</UserControl>
The green label should show the nested view model Name property in the designer, but instead shows the value of the shell view model:
It does bind correctly at runtime:
Example

MVVM Binding a combobox

I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?

Expression Blend and Sample data for Dictionary in WPF application

I have a WPF app which I am using Blend to style.
One of my view models is of the type:
public Dictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents
But when I try to create some sample data in Expression Blend it simply doesnt create the XAML for this property.
Can you create a data type like this in XAML? The non-design time support is killing my productivity.
Regarding your last question: unfortunately, you cannot easily instantiate dictionaries in WPF. I believe this answer explains that part well. The book, WPF 4.5 Unleashed provides a good summary of what the linked answer states:
A common workaround for this limitation (not being able to instantiate
a dictionary in WPF's version of XAML) is to derive a non-generic
class from a generic one simply so it can be referenced from XAML...
But even then, instantiating that dictionary in xaml is again, in my opinion, a painful process. Additionally, Blend does not know how to create sample data of that type.
Regarding the implicit question of how to get design time support: there are a few ways to achieve design time data in WPF, but my preferred method at this point in time for complex scenarios is to create a custom DataSourceProvider. To give credit where it is due: I got the idea from this article (which is even older than this question).
The DataSourceProvider Solution
Create a class that implements DataSourceProvider and returns a sample of your data context. Passing the instantiated MainWindowViewModel to the OnQueryFinished method is what makes the magic happen (I suggest reading about it to understand how it works).
internal class SampleMainWindowViewModelDataProvider : DataSourceProvider
{
private MainWindowViewModel GenerateSampleData()
{
var myViewModel1 = new MyViewModel { EventName = "SampleName1" };
var myViewModel2 = new MyViewModel { EventName = "SampleName2" };
var myViewModelCollection1 = new ObservableCollection<MyViewModel> { myViewModel1, myViewModel2 };
var timeToMyViewModelDictionary = new Dictionary<DateTime, ObservableCollection<MyViewModel>>
{
{ DateTime.Now, myViewModelCollection1 }
};
var viewModel = new MainWindowViewModel()
{
TimesAndEvents = timeToMyViewModelDictionary
};
return viewModel;
}
protected sealed override void BeginQuery()
{
OnQueryFinished(GenerateSampleData());
}
}
All that you have to do now is add your data provider as a sample data context in your view:
<Window x:Class="SampleDataInBlend.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:SampleDataInBlend"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<d:Window.DataContext>
<local:SampleMainWindowViewModelDataProvider/>
</d:Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TimesAndEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<ListBox ItemsSource="{Binding Value}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<TextBlock Text="{Binding EventName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Note: the 'd' in <d:Window.DataContext> is important as it tells Blend and the compiler that that specific element is for design time and it should be ignored when the file is compiled.
After doing that, my design view now looks like the following:
Setting up the problem
I started with 5 classes (2 were generated from the WPF project template, which I recommend using for this):
MyViewModel.cs
MainWindowViewModel.cs
MainWindow.xaml
App.xaml
MyViewModel.cs
public class MyViewModel
{
public string EventName { get; set; }
}
MainWindowViewModel.cs
public class MainWindowViewModel
{
public IDictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents { get; set; } = new Dictionary<DateTime, ObservableCollection<MyViewModel>>();
public void Initialize()
{
//Does some service call to set the TimesAndEvents property
}
}
MainWindow.cs
I took the generated MainWindow class and changed it. Basically, now it asks for a MainWindowViewModel and sets it as its DataContext.
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
MainWindow.xaml
Please note the lack of the design data context from the Solution.
<Window x:Class="SampleDataInBlend.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:SampleDataInBlend"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Grid>
<ListBox ItemsSource="{Binding TimesAndEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<ListBox ItemsSource="{Binding Value}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<TextBlock Text="{Binding EventName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
App.cs
First off, remove StartupUri="MainWindow.xaml" from the xaml side as we'll be launching MainWindow from the code behind.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var viewModel = new MainWindowViewModel();
// MainWindowViewModel needs to have its dictionary filled before its
// bound to as the IDictionary implementation we are using does not do
// change notification. That is why were are calling Initialize before
// passing in the ViewModel.
viewModel.Initialize();
var view = new MainWindow(viewModel);
view.Show();
}
}
Build and run
Now, if everything was done correctly and you fleshed out MainWindowViewModel's Initialize method (I will include my implementation at the bottom), you should see a screen like the one below when you build and run your WPF app:
What was the problem again?
The problem was that nothing was showing in the design view.
My Initialize() method
public void Initialize()
{
TimesAndEvents = PretendImAServiceThatGetsDataForMainWindowViewModel();
}
private IDictionary<DateTime, ObservableCollection<MyViewModel>> PretendImAServiceThatGetsDataForMainWindowViewModel()
{
var myViewModel1 = new MyViewModel { EventName = "I'm real" };
var myViewModel2 = new MyViewModel { EventName = "I'm real" };
var myViewModelCollection1 = new ObservableCollection<MyViewModel> { myViewModel1, myViewModel2 };
var timeToMyViewModelDictionary = new Dictionary<DateTime, ObservableCollection<MyViewModel>>
{
{ DateTime.Now, myViewModelCollection1 }
};
return timeToMyViewModelDictionary;
}
Any more I've gone the route of creating a Design Time Instance of my Viewmodel in my Locator that I reference as #ChrisW suggested above:
d:DataContext="{Binding Source={StaticResource Locator}, Path=DesignTimeVM}"
So I can have some hard-coded values to populate my lists, comboboxes, etc. Makes styling everything that much easier.
I use MVVM Light and so in my ViewModel's constructor I use a pattern like this:
if(IsInDesignMode)
{
ListUsers = new List<User>();
.
.
.
}
The code will only execute at Design Time, and you will have your Xaml UI bound to actual data.
Since Xaml 2009 support generic types, is possible write a loose xaml(can not be compiled in wpf project) like this to represent a dictionary.
Data.xaml
<gnrc:Dictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:gnrc="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:om="clr-namespace:System.Collections.ObjectModel;assembly=System"
x:TypeArguments="sys:DateTime,om:ObservableCollection(x:String)">
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2017/12/31</sys:DateTime>
</x:Key>
<x:String>The last day of the year.</x:String>
<x:String>Party with friends.</x:String>
</om:ObservableCollection>
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2018/1/1</sys:DateTime>
</x:Key>
<x:String>Happy new year.</x:String>
<x:String>Too much booze.</x:String>
</om:ObservableCollection>
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2018/1/10</sys:DateTime>
</x:Key>
<x:String>Just another year.</x:String>
<x:String>Not much difference.</x:String>
</om:ObservableCollection>
</gnrc:Dictionary>
But it is not support by designers like Blend or Visual Studio. If you put it into a xaml that associated with a designer, you will get dozens of errors. To solve this, we need a markup extension to provide value from Data.xaml by using XamlReader.Load method.
InstanceFromLooseXamlExtension.cs
public class InstanceFromLooseXamlExtension : MarkupExtension
{
public Uri Source { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
{
throw new ArgumentNullException(nameof(Source));
}
Uri source;
if (Source.IsAbsoluteUri)
{
source = Source;
}
else
{
var iuc = serviceProvider?.GetService(typeof(IUriContext)) as IUriContext;
if (iuc == null)
{
throw new ArgumentException("Bad service contexts.", nameof(serviceProvider));
}
source = new Uri(iuc.BaseUri, Source);
}
WebResponse response;
if (source.IsFile)
{
response = WebRequest.Create(source.GetLeftPart(UriPartial.Path)).GetResponse();
}
else if(string.Compare(source.Scheme, PackUriHelper.UriSchemePack, StringComparison.Ordinal) == 0)
{
var iwrc = new PackWebRequestFactory() as IWebRequestCreate;
response = iwrc.Create(source).GetResponse();
}
else
{
throw new ArgumentException("Unsupported Source.", nameof(Source));
}
object result;
try
{
result = XamlReader.Load(response.GetResponseStream());
}
finally
{
response.Close();
}
return result;
}
}
This markup extension has a Uri type Source property to let user specify which xaml file to load. Then finally, use the markup extension like this.
MainWindow.xaml
<Window x:Class="WpfApp.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:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{local:InstanceFromLooseXaml Source=/Data.xaml}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Key}">
<ListBox ItemsSource="{Binding Value}"/>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
In this case, I place Data.xaml in application folder, so 'Source=/Data.xaml' will be OK. Every time the designer reloaded(a rebuild will ensure it), the contents in loose xaml will be applied. The result should look like
The loose xaml can contain almost everything, like a ResourceDictionary or something with UiElements. But both Blend or Visual Studio will not check it correctly for you. In the end, hope this is enough for an answer.

How to access a named element of a derived user control in silverlight?

I have a custom base user control in silverlight.
<UserControl x:Class="Problemo.MyBaseControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Border Name="HeaderControl" Background="Red" />
</Grid>
</UserControl>
With the following code behind
public partial class MyBaseControl : UserControl
{
public UIElement Header { get; set; }
public MyBaseControl()
{
InitializeComponent();
Loaded += MyBaseControl_Loaded;
}
void MyBaseControl_Loaded(object sender, RoutedEventArgs e)
{
HeaderControl.Child = Header;
}
}
I have a derived control.
<me:MyBaseControl x:Class="Problemo.MyControl"
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"
mc:Ignorable="d"
xmlns:me="clr-namespace:Problemo"
d:DesignHeight="300" d:DesignWidth="400">
<me:MyBaseControl.Header>
<TextBlock Name="header" Text="{Binding Text}" />
</me:MyBaseControl.Header>
</me:MyBaseControl>
With the following code behind.
public partial class MyControl : MyBaseControl
{
public string Text
{
get; set;
}
public MyControl(string text)
{
InitializeComponent();
Text = text;
}
}
I'm trying to set the text value of the header textblock in the derived control.
It would be nice to be able to set both ways, i.e. with databinding or in the derived control code behind, but neither work. With the data binding, it doesn't work. If I try in the code behind I get a null reference to 'header'. This is silverlight 4 (not sure if that makes a difference)
Any suggestions on how to do with with both databinding and in code ?
Cheers
First of all I'll show you how to adjust your Derived control to handle this. You need to do two things, first you need to implement INotifyPropertyChanged and secondly you to add the binding to the user control.
MyControl Xaml:-
<me:MyBaseControl.Header>
<TextBlock Name="headerItem" />
</me:MyBaseControl.Header>
MyControl code:-
public partial class MyControl : MyBaseControl, INotifyPropertyChanged
{
public MyControl ()
{
InitializeComponent();
headerItem.SetBinding(TextBlock.TextProperty, new Binding("Text") { Source = this });
}
string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged("Text"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
This should get you working. However, as soon as you feel you need to inherit a UserControl based class you should take a step back and ask whether the base and derived items ought to be templated controls instead. If I get time I'll try to add a version of your code in terms of templated controls.

Resources