Reuse XAML in WPF - c#

I have some XAML I want to reuse. I can easily create a custom control and use it, but I'd rather not. Here's what I've tried:
<Window.Resources>
<Expander x:Key="Namespacer" x:Shared="False" Name="NS" Background="SkyBlue">
<StackPanel Name="ClientArea" Margin="20,0,20,0">
<StackPanel Name="Usings" Grid.Row="0" Height="Auto"></StackPanel>
<StackPanel Name="Structs" Grid.Row="1" Height="Auto"></StackPanel>
<StackPanel Name="Classes" Grid.Row="2" Height="Auto"></StackPanel>
<StackPanel Name="IFaces" Grid.Row="3" Height="Auto"></StackPanel>
<StackPanel Name="Delegates" Grid.Row="4" Height="Auto"></StackPanel>
<StackPanel Name="Enums" Grid.Row="5" Height="Auto"></StackPanel>
<StackPanel Name="Nested" Grid.Row="6" Height="Auto"></StackPanel>
</StackPanel>
</Expander>
</Window.Resources>
<StackPanel>
<ContentControl Name="N1" Content="{StaticResource Namespacer}" />
</StackPanel>
Now, I want to do something like:
this.N1.Header = "SomeTitle.Namespace1";
And also be able to add new chunks of XAML to my stack panels in N1 in a similar fashion. How to achieve that?

Well, you could do this:
((Expander)(this.N1.Content)).Header = "SomeTitle.Namespace1";
But that gets ugly. I'd recommend switching to data binding. Here's an example.
First, here's a data class with the structure I think you're going for:
public partial class MainWindow : Window
{
public class MyData
{
public string ItemTitle { get; set; }
public IList<string> Usings { get; set; }
public IList<string> Structs { get; set; }
}
public class MyViewModel
{
public IList<MyData> MyBoundData { get; set; }
}
public MainWindow()
{
var d1 = new MyData{
ItemTitle = "thing1",
Usings = new[]{"a", "b"}
};
var d2 = new MyData{
ItemTitle = "thing2",
Structs = new[]{"c","d"}
};
this.DataContext = new MyViewModel{
MyBoundData = new[]{ d1, d2}
};
InitializeComponent();
}
}
And here's an items control bound to our data:
<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">
<Grid>
<ItemsControl ItemsSource="{Binding MyBoundData}" Focusable="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ItemTitle}" Background="SkyBlue">
<StackPanel>
<Expander Header="Usings" Background="SkyBlue">
<ItemsControl ItemsSource="{Binding Usings}"/>
</Expander>
<Expander Header="Structs" Background="SkyBlue">
<ItemsControl ItemsSource="{Binding Structs}"/>
</Expander>
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Note that the items control has a DataTemplate that corresponds to your "Namespacer" xaml chunk. You could, of course, move the DataTemplate chunk into the window resources like you have in your example, if you want to use it in more than one ItemsControl.

Related

List of objects and ComboBoxes

Here's my problem:
I've got these classes:
public class CsvField
{
public string Content { get; set; }
public CsvField(string content)
{
Content = content;
}
}
public class CsvLine
{
public List<CsvField> Fields = new List<CsvField>();
public int LineNumber;
}
public static class Settings
{
public static List<string> Tags = new List<string>();
public static CsvLine AllHeaders = new CsvLine();
}
What I want to do, is display the ListBox containing every member of Settings.AllHeaders.Fields and a ComboBox containing all members of Settings.Tags list (placed horizontally - a member of AllHeaders on the left and a ComboBox next to it). So if I had 4 headers, I would get a List of those 4 headers and 4 ComboBoxes, each of them next to individual header. Each of these ComboBoxes would contain a list of tags.
So, I defined a DataTemplate:
<Window x:Class="CSV_To_Tags_App.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:CSV_To_Tags_App"
Title="Window2" Height="435" Width="566">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:CsvField}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="HeaderTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap"
VerticalAlignment="Top" Text="{Binding Content}"
/>
<ComboBox HorizontalAlignment="Right" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Label Content="Available headers" HorizontalAlignment="Left"
VerticalAlignment="Top"/>
<ListBox x:Name="HeadersListtListBox" HorizontalAlignment="Left"
Height="254" Margin="36,104,0,0" VerticalAlignment="Top" Width="452"
ItemsSource="{Binding}"/>
</Grid>
</Window>
Now, XAML code above is incomplete, because I don't know how to:
1. Bind TextBlock to Settings.AllHeaders.Fields.Content
2. Bind ComboBox to Tags List
The link provided by #MD's does provide the solution to what you are asking in your question, but you have to rearrange things a little bit to see it.
The XAML below will give you what you are asking for, assuming your class that you are binding to is set up properly for data binding (implementing the INotifyPropertyChanged interface). There are lots of examples on this site of how to properly implement that portion.
<Window x:Class="CSV_To_Tags_App.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:CSV_To_Tags_App"
Title="Window2" Height="435" Width="566">
<Grid>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Path=Settings.AllHeadings.Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Content}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Path=Settings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Tags}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>

Filling a ListBox with an ObservableCollection and display properties of selected items in StackPanel

I'm tring to databind properties of an ObservableCollection to a ListBox (Just the Title property for example).
By clicking on one of the ListItem (with an event ), i'd like to display all the properties of the Collection into a StackPanel. After many tries, I still don't know how can I figure it out...
Here is my code behind :
public partial class TestListView : Window
{
public TestListView()
{
ObservableCollection<Programme> pgr = new ObservableCollection<Programme>();
pgr = readfile();
InitializeComponent();
}
public class Programme
{
public String Title { get; set; }
public String Date { get; set; }
public String Chaine { get; set; }
public Programme(String Title, String Date, String Chaine)
{
this.Title = Title;
this.Date = Date;
this.Chaine = Chaine;
}
}
Here is my XAML :
<Window x:Class="Test.TestListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test;assembly=Test"
Title="TestListView" Height="500" Width="1000" x:Name="Window">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="249*"/>
<ColumnDefinition Width="743*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="428*"/>
<RowDefinition Height="21*"/>
</Grid.RowDefinitions>
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBox Margin="343,0,0,0" x:Name="Recherche"></TextBox>
<Button Height="37" Margin="669,0,0,0" ></Button>
<TextBlock x:Name="t1" Margin="214,0,293,0" Height="33" />
</StackPanel>
</Grid>
</Window>
You need to bind your data to (public) properties. Also, you don't need to use ObservableCollection; any selection changes will be picked up anyhow.
Here's a working sample, with layout and other bits and pieces changed to make it compile for me:
public partial class MainWindow
{
public IList<Programme> pgr { get; }
public MainWindow()
{
pgr = new List<Programme>
{
new Programme("First", "FirstDate", "FirstChaine"),
new Programme("Second", "SecondDate", "SecondChaine"),
new Programme("Third", "ThirdDate", "ThirdChaine"),
};
InitializeComponent();
}
public class Programme
{
// No changes
}
}
...and the XAML:
<Window
x:Name="self"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel DataContext="{Binding ElementName=self}" Orientation="Horizontal">
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Vertical">
<TextBox Margin="10" Text="{Binding ElementName=l1,Path=SelectedItem.Title}" />
<Button Height="37" Margin="0" Content="{Binding ElementName=l1,Path=SelectedItem.Date}"></Button>
<TextBlock Margin="10" Height="33" Text="{Binding ElementName=l1,Path=SelectedItem.Chaine}" />
</StackPanel>
</StackPanel>
</Window>
Note the bindings that reference the selected item:
Text="{Binding ElementName=l1,Path=SelectedItem.Title}"

How do I bind to a property of an object under a collection of collections?

I have an ObservableCollection of a class (TopLevel) that contains a name property and a list of another ObservableCollection of class (BottomLevel) that has just a name property. The binding on the highest list works, but when I try to bind to the property on the BottomList I get nothing. What am I missing here?
XAML:
<Window x:Class="WpfApplication7.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 x:Name="myGrid" DataContext="topList">
<Border BorderBrush="AliceBlue" Grid.Column="0" BorderThickness="5">
<ItemsControl x:Name="ic1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=TopName}"/>
<Border BorderBrush="AntiqueWhite" Grid.Column="1" BorderThickness="5">
<Button Content="{Binding Path=BottomList.BottomName}"/>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
Code behind:
public partial class MainWindow : Window
{
public ObservableCollection<TopLevel> topList;
public MainWindow()
{
InitializeComponent();
topList = new ObservableCollection<TopLevel>();
topList.Add(new TopLevel("T" + (1 + topList.Count).ToString()));
topList[0].AddBottom();
topList.Add(new TopLevel("T" + (1 + topList.Count).ToString()));
topList[1].AddBottom();
ic1.ItemsSource = topList;
}
}
public class TopLevel
{
private ObservableCollection<BottomLevel> bottomList;
private string topName;
public void AddBottom()
{
bottomList.Add(new BottomLevel("B" + (1 + bottomList.Count).ToString()));
}
public TopLevel(string x)
{
bottomList = new ObservableCollection<BottomLevel>();
topName = x;
}
public string TopName
{
get
{
return topName;
}
set
{
if (topName!=value)
{
topName = value;
}
}
}
public ObservableCollection<BottomLevel> BottomList
{
get
{
return bottomList;
}
set
{
if (bottomList!=value)
{
bottomList = value;
}
}
}
}
public class BottomLevel
{
private string bottomName;
public BottomLevel(string x)
{
bottomName = x;
}
public string BottomName
{
get
{
return bottomName;
}
set
{
if (bottomName!=value)
{
bottomName = value;
}
}
}
}
Your path for the button is incorrect. BottomList does not have a "Name" property, so you can't bind to it. Instead, just use BottomName as your path.
Since your topList has a collection of "BottomLevel" you'll need some sort of nested items control to iterate over the "bottomList" collection (then use "BottomName" for your path as above).
As it stands, you basically have:
<ItemsControl //List of TopLevel>
//Your data context is now the TopLevel item itself
<Button Path=/> //What goes here? you have a whole collection of BottomLevel items to choose from!
</ItemsControl>
If you have only one item in BottomList Then you can use the below code for Button
<Button Content="{Binding Path=BottomList[0].BottomName}" Height="50"/>
If you want to Bind the BottomList to some List Control you can bind to the DataGrid then you can use the below code.
<Grid x:Name="myGrid" DataContext="topList">
<Border BorderBrush="AliceBlue" Grid.Column="0" BorderThickness="5">
<ItemsControl x:Name="ic1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=TopName}"/>
<Border BorderBrush="AntiqueWhite" Grid.Column="1" BorderThickness="5">
<DataGrid ItemsSource="{Binding Path=BottomList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding Path=BottomName}" Height="50"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
Please let me know if you need any more help.

Binding from ItemsSource context

I'm having a problem with the DataContext and the Title. The following works as intended:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>
But the Title should contain more information so I'm doing this:
<chartingToolkit:LineSeries ItemsSource="{Binding TrendDaily.Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TrendDaily.Name}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
I figured out the Title binding doesn't work because it has the "Progress" elements as his context but I wasn't able to find a working binding.
Edit:
The complete new code with binding error (Cannot find source for binding with reference 'ElementName=LineName'):
<Window x:Class="WpfApplication1.MainWindow"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<chartingToolkit:Chart Title="Trend">
<chartingToolkit:Chart.Series>
<chartingToolkit:LineSeries DataContext="{Binding TrendDaily}"
ItemsSource="{Binding Progress}" DependentValuePath="Value" IndependentValuePath="Key" x:Name="LineName">
<chartingToolkit:LineSeries.Title>
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="*"/>
</TextBlock>
</chartingToolkit:LineSeries.Title>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public TrendDailyClass TrendDaily { get; set; }
public MainWindow()
{
TrendDaily = new TrendDailyClass();
DataContext = this;
InitializeComponent();
}
}
public class TrendDailyClass
{
public Dictionary<string, double> Progress { get; set; }
public string Name { get; set; }
public TrendDailyClass()
{
Progress = new Dictionary<string, double>();
Progress.Add("10", 10);
Progress.Add("20", 20);
Name = "test";
}
}
Bind TrendDaily to the DataContext of LineSeries, then use DataContext in the inner bindings, using ElementName as:
<chartingToolkit:Chart Title="Trend"
DataContext="{Binding TrendDaily}"
x:Name="LineName">
<chartingToolkit:LineSeries ItemsSource="{Binding Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DataContext.Name, ElementName=LineName}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
Moreover, there is no need to use two TextBlock.. You can use Run (which is very lightweight class) as:
<StackPanel Orientation="Horizontal">
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="-test text"/>
</TextBlock>
</StackPanel>
It's better, as it avoids unnecessary visual element. Classes derived from UIElement are relatively heavier.
If you're first code example is working, you should be able to use the StringFormat property in your first binding:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name, StringFormat='{}{0}-test text'} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>

ItemSource in Tree View MVVM Style... I can't get it to bind properly

I can't seem to use the ItemSource Tag for my treeview. I don't unerstand the problem..
I am trying a simple tree view before i actually bind to my database.. I a looking for an MVVM Style solution
Here is My view Model
public class TreeViewVM : ViewModelBase
{
public class Topic
{
public string Title { get; set; }
public int Rating { get; set; }
private ObservableCollection<Topic> childTopicsValue = new ObservableCollection<Topic>();
public ObservableCollection<Topic> ChildTopics {
get
{
return childTopicsValue;
}
set
{
childTopicsValue = value;
}
}
public Topic() {}
public Topic(string title, int rating)
{
Title = title;
Rating = rating;
}
}
static public ObservableCollection<Topic> Users = new ObservableCollection<Topic>();
public TreeViewVM()
{
Users.Add(new Topic("Using Controls and Dialog Boxes", -1));
Users.Add(new Topic("Getting Started with Controls", 1));
Topic DataGridTopic = new Topic("DataGrid", 4);
DataGridTopic.ChildTopics.Add(
new Topic("Default Keyboard and Mouse Behavior in the DataGrid Control", -1));
DataGridTopic.ChildTopics.Add(
new Topic("How to: Add a DataGrid Control to a Page", -1));
DataGridTopic.ChildTopics.Add(
new Topic("How to: Display and Configure Row Details in the DataGrid Control", 1));
Users.Add(DataGridTopic);
Topics = Users;
}
private ObservableCollection<Topic> _Topics { get; set; }
public ObservableCollection<Topic> Topics
{
get
{
return _Topics;
}
set
{
if (_Topics != value)
{
_Topics = value;
OnNotifyPropertyChanged("Topics");
}
}
}
}
}
Here is my Xaml
xmlns:converter="clr-namespace:TestTree"
xmlns:viewModel="clr-namespace:TestTree.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<UserControl.Resources>
<viewModel:TreeViewVM x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource Topics}}">
<StackPanel x:Name="LayoutRoot2" Background="White">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=Title}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=ChildTopics}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Title}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView Width="400" Height="300" DataContext="{Binding Path=Topics}" ItemTemplate="{StaticResource NameTemplate}" x:Name="myTreeView" />
</StackPanel>
First of all you set DataContext of the "LayourRoot" grid to a resource with key "Topics" which does not exist. That probably should be the "ViewModel" resource. Second of all, why can't you use the ItemsSource property on the TreeView? Setting DataContext property on the TreeView alone will not work. Here is the correct XAML:
<UserControl x:Class="MyUserControl"
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">
<UserControl.Resources>
<viewModel:TreeViewVM x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource ViewModel}}">
<StackPanel x:Name="LayoutRoot2" Background="White">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=Title}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=ChildTopics}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Title}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView Width="400" Height="300" ItemsSource="{Binding Path=Topics}" ItemTemplate="{StaticResource NameTemplate}" x:Name="myTreeView" />
</StackPanel>
</Grid>
</UserControl>

Categories