I'm having trouble understanding how to databind my Songs List<> to a ListBox without needing to set the ItemsSource in the code behind.
It works though, but I would really like to see the List working in the liveview Designer.
namespace App5
{
class SongsData
{
public string Title { get; set; }
public string Lyrics { get; set; }
}
}
And in my MainPage.xaml.cs:
public MainPage()
{
this.InitializeComponent();
List Songs = new List();
Songs.Add(new SongsData() { Title = "Your Song", Lyrics = "It's a little bit funny.." });
Songs.Add(new SongsData() { Title = "Rocket Man", Lyrics = "I'm the Rocket Maaaan.." });
SongsListBox.ItemsSource = Songs;
}
And in the XAML I have a basic ListBox:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Can a friendly person please help me understand what to change - and hopefully why - to get the songs title to show in the ListBox in the liveview Designer of Visual Studio?
With the above I have to Debug the program to see the song titles in the ListBox.
Many thanks in advanced.
You basically need to apply the DesignData build time action to your data files. A very comprehensive walkthrough can be found at msdn.
A quick and simple solution is to move your ListBox to a new UserControl, put the list initialization in the UserControl's constructor, and then add an instance of the UserControl to your main form.
Example:
SongListControl.cs :
namespace App5
{
public parital class SongListControl : userControl
{
this.InitializeComponent();
List Songs = new List();
Songs.Add(new SongsData() { Title = "Your Song", Lyrics = "It's a little bit funny.." });
Songs.Add(new SongsData() { Title = "Rocket Man", Lyrics = "I'm the Rocket Maaaan.." });
SongsListBox.ItemsSource = Songs;
}
}
SongListControl.xaml :
<UserControl x:Class="App5.SongListControl"
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">
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Then in your main window :
<Window x:Class="App5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App5"
Title="MainWindow" Height="350" Width="525">
<Grid>
<app:SongListControl />
</Grid>
</Window>
When you build the project, the constructor initialization will occur in MainWindow preview.
Related
I get this error:- System.NullReferenceException: 'Object reference not set to an instance of an object.'
objectPlacement was null.
private void Button_Click(object sender, RoutedEventArgs e)
{
ObjectPlacement w = new ObjectPlacement() {Topmost = };// ObjectPlacement is new WPF window
objectPlacement.WindowStyle = WindowStyle.None;
settingpanel.Children.Add(objectPlacement);//settingpanel stack is panel name
w.Show();
}
It would be much more usual to define a usercontrol or datatemplate for whatever you're trying to show in your window. A window is a kind of content control. One way to think of a window ( or contentcontrol ) is something that shows you some UI. All the UI in a window is that content.
When you add window to a project it is templated out with a grid in it.
This is the content and everything you want to see in that window goes in it.
You could replace that grid with something else instead.
If you made that a contentpresenter then you can bind or set what that'll show to some encapsulated re-usable UI.
Usually the best way to encapsulate re-usable UI is as a usercontrol.
A datatemplate can reference a usercontrol.
It is not usually your entire UI for a window you want to switch out. But you can and that is occasionally useful - say if you want a generic way to show dialogs.
The usual way to write wpf is mvvm so most devs will want some mvvm way of switching out UI.
I'll show you some code might make the description clearer.
There are some corners cut in what follows, so this is illustrative. Don't just run with this for your next lead developer interview at a stock traders.
But, basically you click a button for Login you "navigate" to a LoginUC view. Click a button for User and you "navigate" to UserUC.
My mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding NavigationViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding VMType}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentPresenter Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
</Window>
Notice the datatemplates which associate the type of a viewmodel with a usercontrol.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview?view=netframeworkdesktop-4.8
What will happen is you present your data in a viewmodel to the UI via that contentpresenter and binding. That viewodel is then templated out into UI with your viewmodel as it's datacontext. The datacontext of a UserUC view will therefore be an instance of UserViewModel. Change CurrentViewModel to an instance of LoginViewModel and you get a LoginUC in your mainwindow instead.
The main viewmodel.
public class MainWindowViewModel : INotifyPropertyChanged
{
public string MainWinVMString { get; set; } = "Hello from MainWindoViewModel";
public ObservableCollection<TypeAndDisplay> NavigationViewModelTypes { get; set; } = new ObservableCollection<TypeAndDisplay>
(
new List<TypeAndDisplay>
{
new TypeAndDisplay{ Name="Log In", VMType= typeof(LoginViewModel) },
new TypeAndDisplay{ Name="User", VMType= typeof(UserViewModel) }
}
);
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Type and display relates the type for a viewmodel with text displayed in the UI.
public class TypeAndDisplay
{
public string Name { get; set; }
public Type VMType { get; set; }
}
This is "just" quick and dirty code to illustrate a principle which is usually called viewmodel first navigation. Google it, you should find a number of articles explaining it further.
For completeness:
<UserControl x:Class="wpf_Navigation_ViewModelFirst.LoginUC"
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:wpf_Navigation_ViewModelFirst"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Background="Yellow">
<TextBlock Text="This is the Login User Control"/>
<TextBox>
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding LoginCommand}"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</UserControl>
public class LoginViewModel
{
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get
{
return loginCommand
?? (loginCommand = new RelayCommand(
() =>
{
string s = "";
}));
}
}
}
<UserControl x:Class="wpf_Navigation_ViewModelFirst.UserUC"
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:wpf_Navigation_ViewModelFirst"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="pink">
<TextBlock Text="This is the User module Control"
VerticalAlignment="Top"
/>
<TextBlock Text="{Binding Path=DataContext.MainWinVMString, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
VerticalAlignment="Bottom"
/>
</Grid>
</UserControl>
public class UserViewModel
{
}
I put this together some years ago, I would now recommend the community mvvm toolkit with it's code generation, base classes, messenger etc.
Just when I thought I was getting better at this, TabControl is now giving me problems. I have read relevant posts here on StackOverflow, but have been unable to get my simple demo application to work the way I want it to.
To keep things focused, I'll start with a single question about something I don't understand.
I have a TabControl whose TabItems each host the same UserControl. When I set the TabControl.ContentTemplate's DataTemplate to my UserControl, a rendering of that control appears, but it looks like it's the same control for each tab. Or perhaps it's not tied to any of the tabs at all.
MainWindow.xaml
<Window x:Class="TabControlMvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localviews="clr-namespace:TabControlMvvm.Views"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind just sets the ViewModel as its DataContext:
namespace TabControlMvvm {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow()
{
InitializeComponent();
DataContext = new TabControlMvvm.ViewModels.MainViewModel();
}
}
}
The TabItem's Content should be another UserControl, PersonMainPanel.xaml:
<UserControl x:Class="TabControlMvvm.Views.PersonMainPanel"
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:localviews="clr-namespace:TabControlMvvm.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border BorderBrush="Red" BorderThickness="2">
<TabControl TabStripPlacement="Bottom">
<TabItem Header="Tab 1">
<localviews:MyTabItem />
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="This was left blank intentionally" />
</TabItem>
<TabItem Header="Tab 3">
<TextBlock Text="This was also left blank intentionally" />
</TabItem>
</TabControl>
</Border>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for PersonMainPanel.xaml
/// </summary>
public partial class PersonMainPanel : UserControl {
public PersonMainPanel()
{
InitializeComponent();
}
}
}
And the MainViewModel:
namespace TabControlMvvm.ViewModels {
public class MainViewModel : ViewModelBase {
public ICollectionView Tabs { get; set; }
public int Selected { get; set; }
public class Person
{
public string Name { get; set; }
}
public class DummyController {
public List<Person> Persons { get; private set; }
public DummyController()
{
Persons = new List<Person> {
new Person { Name = "Larry" },
new Person { Name = "Darryl" },
new Person { Name = "Other brother Darryl" }
};
}
}
public DummyController Controller { get; private set; }
public RelayCommand HelloCommand { get; set; }
public MainViewModel()
{
Controller = new DummyController();
/*
IEnumerable<TabItem> tabs = Enumerable.Range( 1, _controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x),
Content = new PersonMainPanel() });
*/
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x)});
Tabs = CollectionViewSource.GetDefaultView( tabs.ToList());
Tabs.MoveCurrentToFirst();
InitializeCommands();
}
private void InitializeCommands()
{
HelloCommand = new RelayCommand( () => { MessageBox.Show( String.Format( "Hello, Person {0} named {1}!",
Selected, Controller.Persons[Selected].Name)); });
}
}
}
PersonMainPanel hosts another TabControl, where Tab 1's Content is MyTabItem.xaml:
<UserControl x:Class="TabControlMvvm.Views.MyTabItem"
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">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" Width="100" />
</StackPanel>
<Button Command="{Binding HelloCommand}" Content="Say Hello" />
</StackPanel>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for MyTabItem.xaml
/// </summary>
public partial class MyTabItem : UserControl {
public MyTabItem()
{
InitializeComponent();
}
}
}
Which looks like this at runtime:
Issues I have so far:
When I enter Person 1's Name and then click the Person 2 tab, Person 1's Name is still visible, hence my assumption that the controls are not databound properly. I understand that ItemsControls do not pass their DataContext down to their children, but I am not sure how to fix this without associating the View in code-behind.
I would have expected to get databinding errors in the Output window because of the missing DataContext, but I don't get any errors. I assume the DataContext is null, but wouldn't this still result in a binding error?
How can I use Snoop effectively to debug problems like this?
Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm
Here is solution:
In MainWindow modify your TabControl template, to bind Header from your Model:
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
In MyTabItem.xaml, set UpdateTrigger, because default one 'OnLostFocus' can sometimes not save your data:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" />
In MainViewModel modify creating of your tabs, so it will have Name property too:
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format("Person {0}", x), Name = Controller.Persons[x-1].Name });
Also, the most important, create own TabItem class to contain some bounded data:
public class TabItem
{
public string Name { set; get; }
public string Header { set; get; }
}
I am working on trying to learn and understand WPF TreeView and Data Binding in XAML. I cannot seem to grasp the concept of data binding and your help is much appreciated.
The ISSUE is nothing displays in the TreeView. I am mostly interested in understanding how to correctly bind data in WPF to objects for the implementation of a tree view.
Concept: consider a SubjectList with a set of Subjects. Each Subject only has ONE student for the sake of this example
Expected Output in TreeView
Maths
Student 1
Science
Student 2
Arts
Student 3
My current XAML attempt is based on the tutorial read here http://blogs.microsoft.co.il/pavely/2014/07/12/data-binding-for-a-wpf-treeview/.
Following this I have the following 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView x:Name="treeView" HorizontalAlignment="Left"
Height="284"
Margin="18,10,0,0"
VerticalAlignment="Top"
Width="115"
ItemsSource="{Binding SubjectList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubjectList}"
DataType="{x:Type local:Subject}">
<TreeViewItem Header="{Binding SubjectName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
MainWindow function to add Student Objects into a StudentList:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Student s1 = new Student("Alex", 1);
Student s2 = new Student("Kevin", 2);
Student s3 = new Student("Sina", 3);
Student s4 = new Student("Evan", 4);
Subject a1 = new Subject("Maths", 1);
Subject a2 = new Subject("Science", 2);
Subject a3 = new Subject("Arts", 3);
a1.setStudent(s1);
a2.setStudent(s2);
a3.setStudent(s3);
Subjects list = new Subjects();
list.AddSubjects(a1);
list.AddSubjects(a2);
list.AddSubjects(a3);
DataContext = list;
}
}
Classes
class Subjects
{
private List<Subject> subjectList;
public List<Subject> SubjectList { get; set;}
public Subjects()
{
SubjectList = new List<Subject>();
}
public void AddSubjects(Subject s)
{
SubjectList.Add(s);
}
}
class Subject
{
private String subjectName;
private List<Student> studentList;
//accessor methods
public String SubjectName { get; set; }
public List<Student> StudentList { get; }
public Subject()
{
}
public Subject(string name, int id)
{
SubjectName = name;
StudentList = new List<Student>();
}
public void setStudent(Student aStudent)
{
StudentList.Add(aStudent);
}
}
class Student
{
public Student()
{
}
public Student(string name, int id)
{
StudentName = name;
StudentID = id;
}
private String studentName;
private int studentID;
//accessor methods
public String StudentName { get; set;}
public int StudentID { get; set; }
}
}
What am i doing wrong? Anyone who can point me in the right direction of better understanding the concept of data binding in WPF to a list of objects would be a massive help in my self study!
Update 1: setting DataContext = list; and removing the DataContext reference in XAML resolved the issue of having two View's defined.
Question 2:: I am still a bit confused with View's. I have added a List as a property of a Subject Class.
How would you retrieve the Student Name from the List of Student objects which are within a Subject Object in XAML? Do you require a View for any/every collection you wish to show in the TreeView? I wish to learn how these pieces all work together. Any further material or assistance greatly appreciated.
You are creating two instances of your Subjects view model, one in XAML
<Window.DataContext>
<local:Subjects/>
</Window.DataContext>
and one in code
Subjects list = new Subjects();
Adding items to the list instance in code behind won't add them to the instance in the DataContext.
Change your code like this:
var list = (Subjects)DataContext;
list.AddSubjects(a1);
...
Or remove the DataContext assignment from your XAML and write the code behind like this:
var list = new Subjects();
list.AddSubjects(a1);
...
DataContext = list;
That said, it may make sense to use ObservableCollection instead of List to notify about collection changes, e.g. while adding or removing subjects.
So I believe your confusion is rooted in the concept of binding so I will give a crack at trying to clarify it for you.
Take this code you posted:
<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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView x:Name="treeView" HorizontalAlignment="Left"
Height="284"
Margin="18,10,0,0"
VerticalAlignment="Top"
Width="115"
ItemsSource="{Binding SubjectList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubjectList}"
DataType="{x:Type local:Subject}">
<TreeViewItem Header="{Binding SubjectName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
In your code behind you set the datacontext to an instance of Subjects. That means the window's datacontext is Subjects.
Now as you go deeper into the xaml you reach the stackpanel. This inherits the same datacontext so the stackpanel's datacontext is still Subjects.
Now you get to the treeview. The treeview's datacontext is still Subjects which is why you are able to bind the ItemsSource property to SubjectList, a property on the Subjects class.
Now when you get to the TreeView.ItemTemplate, the datacontext is a single item from SubjectList, aka an instance of Subject.
The HierachicalDataTemplate is still using the same context of an instance of Subject. You can't bind the HierarchicalDataTemplate's ItemsSource to SubjectList because the Subject class does not have that property.
You can bind to StudentList though since the Subject class does have that property.
If you were to bind the ItemsSource to StudentList you could then inside the HierarchicalDataTemplate put a TextBox with the binding to subject name since inside of the HierachicalDataTemplate has the DataContext of a singular StudentItem
So finally the working code would look like this:
<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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView x:Name="treeView" HorizontalAlignment="Left"
Height="284"
Margin="18,10,0,0"
VerticalAlignment="Top"
Width="115"
ItemsSource="{Binding SubjectList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding StudentList}">
<TextBox Text="{Binding StudentName}"/>
<TextBox Text=" "/>
<TextBox Text="{Binding ID}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
So all in all you were very close, but I hope this helps you understand what is going on when you go deeper into the bindings. If you are confused about anything leave a comment and I will try to make it clearer
What I'm trying to achieve is somewhat easy in web applications but I have been struggling to do it in WPF.
I want to load a paragraph of text in WPF and replace some of its specific words with editable textboxes. How can I do that?
What is the best strategy for getting this in a right a clean way?
Update:
Consider the following text. I want to display it in WPF and instead of the bold words put some textboxes.
Do you know someone rich and famous? Is he confident, popular, and
joyful all of the time—the epitome of mainstream success? Or,
on the other hand, is he stressed, having second thoughts about his life choices, and unsure about the meaning of his life?
WPF and XAML, unlike HTML, are all about data.
The best way to think and reason about any XAML-based UI is to think about the data that you need to show and interact with.
In this case:
public class Word
{
public string Value { get; set; }
public bool IsEditable { get; set; }
}
would represent each of our words. Then you just need a List of these:
public class ViewModel
{
public List<Word> Words { get; private set; }
public ViewModel()
{
var editableWords = new[] { "on", "of" };
var text = "Do you know someone rich and famous? Is he confident, popular, and joyful all of the time—the epitome of mainstream success? Or, on the other hand, is he stressed, having second thoughts about his life choices, and unsure about the meaning of his life?";
this.Words =
text.Split(' ')
.Select(x => new Word
{
Value = x,
IsEditable = editableWords.Contains(x.ToLower())
})
.ToList();
}
}
Notice how I'm turning the text into a List<Word> and setting IsEditable where desired.
Now it's just a matter of using an ItemsControl to show these:
<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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Words}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5,2,5,2">
<TextBlock Text="{Binding Value}"
VerticalAlignment="Center"/>
<TextBox Text="{Binding Value}"
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVis}}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Finally, set the DataContext to an instance of our ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Result:
Notice that I'm not "touching" the UI in code at all, this is all just simple, simple properties and DataBinding.
I have an ObservableCollection<IRuleCondition> that I want to display - the IRuleCondition interface is used by 2 different classes I want to display, a RuleCondition that simply displays one rule condition (info such as priority, property to check and so on), and a RuleConditionGroup, that can contain 2 or more RuleConditions, grouped in such a way that any of the conditions could match, or all etc.
In the XAML I was wondering is there a way to display a different ListView.ItemTemplate depending on what the type is that it encounters in the ObservableCollection<IRuleCondition>? Or would I need to implement two different ObservableCollections?
Here is a simple example of how this works
This is how the objects are defined
public interface Person
{
string Name { get; set; }
}
public class Manager : Person
{
public string Name { get; set; }
}
public class Employee : Person
{
public string Name { get; set; }
public string ManagerName { get;set;}
}
This is the MainWindow code behind
public partial class MainWindow : Window
{
ObservableCollection<Person> mPeople = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get
{
return mPeople;
}
}
public MainWindow()
{
DataContext = this;
mPeople.Add( new Employee{ Name = "x" , ManagerName = "foo"});
mPeople.Add( new Manager { Name = "y"});
InitializeComponent();
}
}
This is the 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:my="clr-namespace:WpfApplication1"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type my:Employee}">
<StackPanel Background="Green" Width="300">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ManagerName}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Manager}">
<StackPanel Background="Red"
Width="300">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding People}"></ListBox>
</Grid>
</Window>
As you can see there are two datatemplates one for Manager and one for Employee
And this is how the crappy output looks like. Notice the green and red background and extra field displayed for the Employee compared to the manager
Just define two different DataTemplates in the Resources section, one for each RuleCondition type.
1) Create your two different data templates, just as you say you've already done.
2) Create a custom DataTemplateSelector to choose the appropriate template.
One of your comments states that you're getting an error from your DataTemplateSelector. Verify that you're implementing the class correctly, perhaps paste your implementation. It should be fairly small and straightforward.