WPF binding subcontrols to a parent control - wpf

I am trying to perform databinding, given the following case:
I have a class called "Node" that has two properties: "speed" and "pauseTime". I then have an array of Nodes.
In my XAML, I have a numeric control (labeled Node) that allows the user to switch between Nodes. There are also two subcontrols which I want to have show speed and pauseTime for the selected Node.
My question is how do I set the databinding for speed, for example, so that it shows the speed for the selected Node in the Nodes array based on the value in the Node numeric control?
I tried googling this, but am not sure what search terms to use.

I'm not sure what you mean by numeric control, but I knocked up an example of a master details control scenario.
In the XAML you have a Grid with a ListView (the master), and a TextBlock (the details):
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding}" x:Name="masterListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Row="1" Text="{Binding ElementName=masterListView, Path=SelectedItem.Speed}" />
</Grid>
The code behind looks like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Node[]
{
new Node() { Speed = 1, PauseTime = "1 Min", Name = "Item 1" },
new Node() { Speed = 2, PauseTime = "2 Mins" , Name = "Item 2" }
};
}
}
public class Node
{
public int Speed { get; set; }
public string PauseTime { get; set; }
public string Name { get; set; }
}
The child TextBlock binds to the Speed property of the Selected node. If you add IsSynchronizedWithCurrentItem="True" to the ListView, the first item will automatically be selected when the window is loaded. If you Google master details/WPF and IsSynchronizedWithCurrentItem="True" you will find more details.
It would probably also be useful to Google how to do this using MVVM- a reasonable approach is mentioned in this Stackoverflow Answer.

Related

WPF - How to implement DataTemplate with complex logic?

I am currently transferring my app from WinForms to WPF.
Since I'm new in WPF, I stucked at creating DataTemplates for my treeView items. The screenshot shows how my treeview looked in WinForms version, and I need to get close result in WPF.
(My WinForms treeview)
As you can see, my DataTemplate's logic should take into account these factors:
Node type / defines which icon and fields combination will be displayed for particular item (node). App has about 7-8 node types. Type stored in separate node's field.
Variable values / I need to replace with text if null, etc
Numeric variable values / e.g.: set gray color if zero, etc.
Other properties / e.g.: adding textblocks depending on boolean fields.
And so on...
All these factors result into huge amount of possible item params combinations.
Also I'm using DevComponents WPF DotNetBar AdvTree to divide item properties into columns. I presume I should create 'sub templates' for different field sets and compose from them the entire DataTemplate for each column.
I've learned about triggers, and have to say that implementing my logic with triggers will make my subtemplates huge anyway.
(Current state of my WPF treeview)
So here are my questions:
Are there any ways to dynamically compose complex templates with C# code (without creating raw XAML and loading it at runtime)?
Maybe I should use completely different way (instead of using DataTemplate)? In Winforms I just used OwnerDraw mode, so the task was MUCH easier than in WPF :(
And how to display nested properties inside template? e.g.: Item.Prop.Subprop1.Subprop2.Targetprop.
PS: English is not my first language, sorry for your eyes.
1) The answer is yes.
For exemple if you want to define a template in your window for a simple string
public MainWindow()
{
InitializeComponent();
DataTemplate template = new DataTemplate(typeof(string));
FrameworkElementFactory borderFactory = new FrameworkElementFactory(typeof(Border));
borderFactory.SetValue(Border.PaddingProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderBrushProperty, Brushes.Red);
FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
textFactory.SetBinding(TextBlock.TextProperty, new Binding
{
Mode = BindingMode.OneWay
});
borderFactory.AppendChild(textFactory);
template.VisualTree = borderFactory;
myControl.ContentTemplate = template;
}
And in the Window you just put something like
<ContentControl x:Name="myControl" Content="Test text" Margin="10"/>
Your content control will render the string surrounded by a red border.
But as you anc see it is really complex to define your templates in this way.
The only scenario where i could imagine this approache is for some kind of procedurally generated templates.
Another way is to generate a string for the template and then load it with XamlReader:
string xaml = "<Ellipse Name=\"EllipseAdded\" Width=\"300.5\" Height=\"200\"
Fill=\"Red\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>";
object ellipse = XamlReader.Load(xaml);
2) I don't really see the need to generate templates in code behind. For exemple for this kind of data structure:
public class User
{
public string Name { get; set; }
public User Friend { get; set; }
}
public class RootNode
{
public string Title { get; set; }
public User User { get; set; }
public List<Node> Nodes { get; set; }
}
public class Node
{
public string Title { get; set; }
public List<SubNode> SubNodes { get; set; }
}
public class SubNode
{
public string Title { get; set; }
}
You can define this type of template:
<Window
...
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RootNode}" ItemsSource="{Binding Nodes}">
<StackPanel x:Name="spContainer" Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding User}" Value="{x:Null}">
<Setter TargetName="spContainer" Property="Background" Value="Yellow"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding SubNodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:SubNode}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding RootNodes}"/>
</Grid>
</Window>
As you can see you can define a template by data type, you can also use triggers to modify the behavior in specific cases, you could also use som binding converters...
3) You can bind to nested properties just like to normal ones :
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
However in some cases more than two level bindings could fail (fail to resolve or fail to update when property changes, ...)

Get .Text property from dynamically added Textbox

I am dynamically adding several textboxes to my grid in my code behind. I would like to be able to capture what the user enters into those textboxes.
I'm not quiet sure how to do this as the name of the dynamically added textbox is not available when I try to add it in my codebehind.
I want to create a querybuilder tool. This is very rudementary but basically I want to add multiple comboboxes, textboxes and buttons.
First of all, you must leave behind the traditional mentality of manipulating UI elements in code and Embrace MVVM
<Window x:Class="MiscSamples.QueryBuilderSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="QueryBuilderSample" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Operators}"
SelectedItem="{Binding Operator}"/>
<TextBox Text="{Binding Value}" Grid.Column="1"/>
<Button Content="Add" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code behind:
public partial class QueryBuilderSample : Window
{
public List<QueryCriteria> Criterias { get; set; }
public QueryBuilderSample()
{
InitializeComponent();
DataContext = Criterias = Enumerable.Range(0, 10).Select(x => new QueryCriteria()).ToList();
}
}
ViewModel:
public class QueryCriteria
{
public List<Operators> Operators
{
get
{
return Enum.GetValues(typeof(Operators))
.Cast<Operators>()
.ToList();
}
}
public Operators Operator { get; set; }
public string Value { get; set; }
}
public enum Operators
{
Equals,
Contains,
GreaterThan,
SmallerThan,
}
Result:
Notice that I'm not doing a single line of code to create / manipulate UI elements. Everything is done via DataBinding.
Simple code. No complex event handling stuff or anything like that.
Declarative Code. Just Simple, Simple Properties and INotifyPropertyChanged. That's the default approach to EVERYTHING in WPF.
When you insert a text box, keep a reference to it in some object. A dictionary could be a good choice. That way, you can get that reference later, and from that reference you can read its Text property.
#jeff V you can simply capture the textbox text by using the name your assigning those textboxes..
you are using tb1,tb2 as textboxes name...so you can easily get the values using

How do I bind a Listview SelectedItem to a Textbox using the TwoWay mode?

I am very new to WPF and testing some things that I would like to include in an application that I will be working on. I have a 2 row ListView (bound to a textbox) with the names Scott Guthrie and Jon Skeet in it. I am trying to select "Scott Guthrie" in the ListView and have it populate the TextBox. I want to be able to edit the text and tab off and have the ListView updated.
Edit:I removed the code since that really didn't add anything to the question.
Wow, that's really complicated what you've got there.
This can be accomplished in a very simple way. You need a model to represent the programmer, a view model to hold a list of programmers, and simple binding to take care of the rest.
The model:
public sealed class Programmer
{
public string Name { get; set; }
}
Its very simple. An object representing a programmer with a name. We must encapsulate the name within an object because strings are immutable in .NET. If you tried binding against a single string in a list of strings, changes wouldn't propagate.
The collection of programmers is kept in a ViewModel. In this case, I call it ViewModel, because I have no imagination. This view model contains everything that the view binds against. In this case, its the list of programmers.
public sealed class ViewModel
{
public ObservableCollection<Programmer> Programmers { get; private set; }
public ViewModel()
{
Programmers = new ObservableCollection<Programmer>();
}
}
The ViewModel is set as the DataContext of our view. The DataContext flows down the visual tree, and we can bind against it at any point.
public MainWindow()
{
var vm = new ViewModel();
vm.Programmers.Add(new Programmer { Name = "Jon Skeet" });
vm.Programmers.Add(new Programmer { Name = "Scott Guthrie" });
DataContext = vm;
InitializeComponent();
}
You can set the DataContext in any way you want; I'm doing it here for simplicity's sake.
In the UI, I simply bind the ListView against the list of Programmers in the ViewModel (the DataContext, unless otherwise stated, is the root of the binding path). I then bind the TextBox against the SelectedItem of the ListBox. You select a Programmer from the list, which then becomes the SelectedItem, which I can then change the Name of.
<Window
x:Class="Programmers.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:t="clr-namespace:Programmers"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox
x:Name="list"
ItemsSource="{Binding Programmers}"
DisplayMemberPath="Name" />
<TextBox
Grid.Column="1"
VerticalAlignment="Top"
Text="{Binding SelectedItem.Name, ElementName=list}" />
</Grid>
</Window>
Simple, once you get the hang of it.
This works (except that you need to validate the textbox since you can enter any text.. a dropdown might be a better choice).
View:
<TabItem x:Name="RightTabPage" Header="RightModel" DataContext="{Binding Right}">
<StackPanel>
<TextBox Text="{Binding SelectedGuru}"/>
<ListView SelectedItem="{Binding SelectedGuru}" ItemsSource="{Binding Gurus}"/>
</StackPanel>
</TabItem>
ViewModel:
public class RightViewModel
{
public RightViewModel()
{
Gurus = new[] {"Scott Guthrie", "Jon Skeet"};
SelectedGuru = Gurus.First();
}
public string SelectedGuru { get; set; }
public IEnumerable<string> Gurus{ get; set; }
}

Programmatically Add Controls to WPF Form

I am trying to add controls to a UserControl dynamically (programatically). I get a generic List of objects from my Business Layer (retrieved from the database), and for each object, I want to add a Label, and a TextBox to the WPF UserControl and set the Position and widths to make look nice, and hopefully take advantage of the WPF Validation capabilities. This is something that would be easy in Windows Forms programming but I'm new to WPF. How do I do this (see comments for questions) Say this is my object:
public class Field {
public string Name { get; set; }
public int Length { get; set; }
public bool Required { get; set; }
}
Then in my WPF UserControl, I'm trying to create a Label and TextBox for each object:
public void createControls() {
List<Field> fields = businessObj.getFields();
Label label = null;
TextBox textbox = null;
foreach (Field field in fields) {
label = new Label();
// HOW TO set text, x and y (margin), width, validation based upon object?
// i have tried this without luck:
// Binding b = new Binding("Name");
// BindingOperations.SetBinding(label, Label.ContentProperty, b);
MyGrid.Children.Add(label);
textbox = new TextBox();
// ???
MyGrid.Children.Add(textbox);
}
// databind?
this.DataContext = fields;
}
Alright, second time's the charm. Based on your layout screenshot, I can infer right away that what you need is a WrapPanel, a layout panel that allows items to fill up until it reaches an edge, at which point the remaining items flow onto the next line. But you still want to use an ItemsControl so you can get all the benefits of data-binding and dynamic generation. So for this we're going to use the ItemsControl.ItemsPanel property, which allows us to specify the panel the items will be put into. Let's start with the code-behind again:
public partial class Window1 : Window
{
public ObservableCollection<Field> Fields { get; set; }
public Window1()
{
InitializeComponent();
Fields = new ObservableCollection<Field>();
Fields.Add(new Field() { Name = "Username", Length = 100, Required = true });
Fields.Add(new Field() { Name = "Password", Length = 80, Required = true });
Fields.Add(new Field() { Name = "City", Length = 100, Required = false });
Fields.Add(new Field() { Name = "State", Length = 40, Required = false });
Fields.Add(new Field() { Name = "Zipcode", Length = 60, Required = false });
FieldsListBox.ItemsSource = Fields;
}
}
public class Field
{
public string Name { get; set; }
public int Length { get; set; }
public bool Required { get; set; }
}
Not much has changed here, but I've edited the sample fields to better match your example. Now let's look at where the magic happens- the XAML for the Window:
<Window x:Class="DataBoundFields.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataBoundFields"
Title="Window1" Height="200" Width="300">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Grid>
<ListBox x:Name="FieldsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" VerticalAlignment="Center"/>
<TextBox Width="{Binding Length}" Margin="5,0,0,0"/>
<Label Content="*" Visibility="{Binding Required, Converter={StaticResource BoolToVisConverter}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualHeight}"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualWidth}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
First, you will notice that the ItemTemplate has changed slightly. The label is still bound to the name property, but now the textbox width is bound to the length property (so you can have textboxes of varying length). Furthermore, I've added a "*" to any fields that are required, using a simplistic BoolToVisibilityConverter (which you can find the code for anywhere, and I will not post here).
The main thing to notice is the use of a WrapPanel in the ItemsPanel property of our ListBox. This tells the ListBox that any items it generates need to be pushed into a horizontal wrapped layout (this matches your screenshot). What makes this work even better is the height and width binding on the panel- what this says is, "make this panel the same size as my parent window." That means that when I resize the Window, the WrapPanel adjusts its size accordingly, resulting in a better layout for the items.
It is not recommended to add controls like this. What you ideally do in WPF is to put a ListBox(or ItemsControl) and bind your Business object collection as the itemsControl.ItemsSource property. Now define DataTemplate in XAML for your DataObject type and you are good to go, That is the magic of WPF.
People come from a winforms background tend to do the way you described and which is not the right way in WPF.
I would listen to Charlie and Jobi's answers, but for the sake of answering the question directly... (How to add controls and manually position them.)
Use a Canvas control, rather than a Grid. Canvases give the control an infinite amount of space, and allow you to position them manually. It uses attached properties to keep track of position. In code, it would look like so:
var tb = new TextBox();
myCanvas.Children.Add(tb);
tb.Width = 100;
Canvas.SetLeft(tb, 50);
Canvas.SetTop(tb, 20);
In XAML...
<Canvas>
<TextBox Width="100" Canvas.Left="50" Canvas.Top="20" />
</Canvas>
You can also position them relative to the Right and Bottom edges. Specifying both a Top and Bottom will have the control resize vertically with the Canvas. Similarly for Left and Right.

How to make gridview a child element of a treeview in wpf application

I am trying to populate a datagrid (or gridview) as a child elment of a treeview from the database. I am able to get data from the DB in the tree, however, it doesnt seem to work for the datagrid. Here is my xaml code:
<Window x:Class="AttemptUsingHirarchichalData.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:AttemptUsingHirarchichalData"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type data:Root}"
ItemsSource="{Binding Path=RootList}">
<TextBlock Text="{Binding RootNode}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type data:Nodes}"
ItemsSource="{Binding Path=ChildList}">
<TextBlock Text="{Binding ChildNode}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="TreeView1">
<TreeViewItem ItemsSource="{Binding Path=RootList}"
Header="{Binding RootNode}"/>
<TreeViewItem ItemsSource="{Binding Path=dt_Age}"
Header="{Binding dt_Age}"/>
</TreeView>
</Grid>
My codebehind is something like this:
InitializeComponent();
Root obj_Root = new Root();
obj_Root.RootNode = "RootNode";
obj_Root.RootList = new List<Nodes>();
Class1 obj_Class1 = new Class1();
DataTable dt_Age = obj_Class1.GetAgeInComboBox();
for (int i = 0; i < dt_Age.Rows.Count; i++)
{
Nodes obj_AgeNode = new Nodes();
obj_AgeNode.ChildNode = dt_Age.Rows[i][0].ToString();
obj_Root.RootList.Add(obj_AgeNode);
Class1 obj_class = new Class1();
DataTable dt_name = new DataTable();
dt_name = obj_class.GetName(Convert.ToInt32(dt_Age.Rows[i][0]));
obj_AgeNode.ChildList = new List<Nodes>();
//gridv
for (int j = 0; j < dt_name.Rows.Count; j++)
{
Nodes obj_NameNode = new Nodes();
obj_NameNode.ChildNode = dt_name.Rows[j][0].ToString();
obj_AgeNode.ChildList.Add(obj_NameNode);
}
}
TreeView1.DataContext = obj_Root;
My Class file has this as a part of it:
public class Nodes
{
public string ChildNode { get; set; }
public List<Nodes> ChildList { get; set; }
}
public class Root
{
public string RootNode { get; set; }
public List<Nodes> RootList { get; set; }
}
public DataTable GetAgeInComboBox()
{
SqlDataAdapter da_Age = new SqlDataAdapter("select distinct Age from myfrstattemt", conn1);
DataTable dt_Age = new DataTable();
da_Age.Fill(dt_Age);
return dt_Age;
}
Please tell me how to implement it. I am new to this, so please excuse my stupid errors, and please try to explain in simple terms. Thank you.
This is what I actually need to do
The good news is that you're doing a lot more work here than you need to, which is probably why you're having trouble.
The bad news is that you should really study a little more about WPF to properly grok this and come up with a good approach that's clean and concise. I'll try and point you in the right direction.
Firstly, you should get your head around ItemsControl. It's a really powerful class and is the base class of many of the everyday controls you would use in a WPF application. You should understand how binding any collection (IEnumerable, IList, IBindingList etc) to the ItemsSource property of ItemsControl will cause child items to be created.
You should then understand (if you don't already) how data types are converted into UI elements via DataTemplates. This is a simple but powerful concept.
Then you should experiment with a small extension to the above, namely HeaderedItemsControl and HierarchicalDataTemplate. This will give you all the tools you need to use the TreeView in the way you want to.
At no point would you need to create any TreeViewItems in C# code. If you can get the underlying data objects to reflect the hierarchy you want to display (irrespective of whether each node is a simple text label or a data grid) then you can create hierarchical data templates for all levels and have WPF take care of binding everything and creating the TreeViewItems for you.
EDIT
I have some questions for your edited question:
What is the difference between Root and Nodes?
Do you have a class hierarchy that models the relationship between nodes? If so, just use that rather than copying the objects into instances of Root and Nodes. I'll give you a made up example.
Let's assume you have Customers who place Orders, and each order has Items.
public class Customer
{
public string Name { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
public class Order
{
public DateTime PurchaseDate { get; set; }
public IEnumerable<OrderItem> Items { get; set; }
}
public class OrderItem
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public double UnitPrice { get; set; }
public double TotalPrice { get; set; }
}
The above types represent a hierarchy. If you have a structure like this, then you can bind it directly to the UI. You don't need to create any Root or Node objects. This is the WPF way :)
(Note that if you don't have the above class hierarchy, you might set about creating one specifically for use in the UI. Read more about the MVVM pattern if you're interested.)
In your XAML you would define the TreeView as:
<TreeView x:Name="_treeView" ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type data:Customer}"
ItemsSource="{Binding Path=Orders}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:Order}">
<StackPanel>
<TextBlock Text="{Binding PurchaseDate}"/>
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding ProductName}" />
<GridViewColumn DisplayMemberBinding="{Binding Quantity}" />
<GridViewColumn DisplayMemberBinding="{Binding UnitPrice}" />
<GridViewColumn DisplayMemberBinding="{Binding TotalPrice}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And in code-behind, you'd do something like this:
_treeView.DataContext = customers; // eg. IEnumerable<Customer>
Might be worth having a look at this post by Marlon Grech.

Resources