How do I bind to something outside a datacontext - wpf

I have a listbox in WPF that is in the Layout Root.
I also have a Frame that is in the Layout Root as well.
The listbox is composed of items that have a string(Name) and a framework element(UI).
How do I bind the frame's content to be the UI property of the listbox's selected item property?
If you need a codebehind, how would you do this in MVVM

I used a ContentControl instead of Frame since I had problem binding to Content property, I never got it to refresh after binding changed. I didn't do proper MVVM, Data should not be hosted inside the view.
XAML:
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding}" />
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource CVS}}">
<ListBox
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name">
</ListBox>
<ContentControl Content="{Binding Path=UI}" />
</StackPanel>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace BindDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Data = new List<DataItem>();
Data.Add(new DataItem("TextBox", new TextBox(){ Text="hello" }));
Data.Add(new DataItem("ComboBox", new ComboBox()));
Data.Add(new DataItem("Slider", new Slider()));
DataContext = Data;
}
public List<DataItem> Data
{
get; private set;
}
}
public class DataItem
{
public DataItem(string name, FrameworkElement ui)
{
Name = name;
UI = ui;
}
public string Name { get; private set; }
public FrameworkElement UI { get; private set; }
}
}

It sounds as you want to display list of objects and details for selected object. If I am right, solution in MVVM may be following:
<ListView ItemsSource="{Binding ObjectsList}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding ObjectsList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<!-- details template -->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

Related

Images disappear from the ListView in WPF after an action

Images load correctly to the listview, but whenever I click on the listview and change the SelectedItem, the images dissapear. I can see the elements and do everything with them, but they are a simple row and the image itself is not in the row.
This is the XAML for the ListView
<ListView Name="testLB" ItemsSource="{Binding Images}" SelectedItem="{Binding SelectedImage}" HorizontalAlignment="Left" Height="393" Margin="627,28,0,0" VerticalAlignment="Top" Width="244" SelectionChanged="testLB_SelectionChanged"/>
And this is part of my ViewModel.
public ObservableCollection<Image> Images { get; private set; }
public Image SelectedImage { get => selectedImage;
set {
this.selectedImage = value;
this.OnPropertyChanged("SelectedImage");
} }
private Image selectedImage;
Image is a subclass of UIElement, and should hence not be used as item type in a view model.
Use ImageSource instead (and add e.g. BitmapImages or BitmapFrames to your collection):
public ObservableCollection<ImageSource> Images { get; private set; }
public ImageSource SelectedImage { ... }
Then make your ListView's ItemTemplate contain an Image element:
<ListView ItemsSource="{Binding Images}" SelectedItem="{Binding SelectedImage}" ...>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Setting DataGrid and ContextMenu with Same DataContext

I am trying to dynamically create a Context Menu for a data grid that passes in the selected menu item (Mvvm). I want the DataContext to be the same for the for the context menu as the grid.
I have the following Xaml
<Grid>
<!-- Cut some other stuff -->
<Border Grid.Row="2" BorderBrush="Black" BorderThickness="1">
<DataGrid x:Name="MyDataGrid" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=GridData}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="None" SelectionUnit="Cell" SelectionMode="Single">
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuActions}">
<MenuItem Header="{Binding DisplayName}" Command="{Binding Command}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="DataOne" Width="130" Binding="{Binding Path=DataOne}"/>
<DataGridTextColumn Header="DataTwo" Width="100" Binding="{Binding Path=DataTwo}"/>
<DataGrid.Columns>
<DataGrid.InputBindings>
<MouseBinding Gesture="RightClick" Command="{Binding DataGridRightClick}" CommandParameter="{Binding ElementName=MyDataGrid, Path=SelectedCells}" />
</DataGrid.InputBindings>
</DataGrid>
</Border>
</Grid>
and the following classes for my data context that I use for my datagrid and would also like to use for my context menu (I have cut out all the data grid population code for readability ) and menuitems
public class DataViewModel : ViewModelBase // Implements INotifyPropertyChanged
{
// All code that populates the grid has been removed
public ObservableCollection<MenuItemViewModel> ContextMenuActions { get; set; }
public ICommand DataGridRightClick { get; private set; }
public MarketDataViewModel()
{
DataGridRightClick = new RelayCommand(RightClick);
}
public void RightClick(object obj)
{
MenuItemViewModel menuItem = new MenuItemViewModel("Test", new RelayCommand(TestCall));
ContextMenuActions.Add(menuItem); // I ensure this is added on the gui thread
MenuItemViewModel menuItem1 = new MenuItemViewModel("Test2", new RelayCommand(TestCall));
ContextMenuActions.Add(menuItem1); // I ensure this is added on the gui thread but for
}
private void TestCall(object obj)
{
// want to pass in the selected menuitem
}
}
public class MenuItemViewModel
{
public MenuItemViewModel(string displayName,ICommand command)
{
DisplayName = displayName;
Command = command;
}
public string DisplayName { get; set; }
public ICommand Command { get; set; }
}
Currently when I click on the datagrid the right click event gets called and runs fine but an empty context menu appears on the grid.
Thanks,
Nick
You need to bind to the DataContext.ContextMenuActions of the parent DataGrid. The easiest way to achieve this is by using the following binding:
<ContextMenu ItemsSource="{Binding DataContext.ContextMenuActions, ElementName=MyDataGrid}" ...

How to bind to a child branch or leaf of a treeview (in MVVM style)?

I'm a newbie to TreeViews. In WPF style, I have a TreeView organized in three levels:
ReportName1
NetworkName1
PrinterName1
PrinterName2
NetworkName2
PrinterName3
PrinterName4
ReportName2
....
in the XAML, I am using Interaction behaviors to bind the TreeView SelectedItem to the ViewModel:
<TreeView ItemsSource="{Binding ReportTree}" >
<i:Interaction.Behaviors>
<tvb:TreeViewBehavior SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}" />
</i:Interaction.Behaviors>
At this point, all works well to send an item from the ReportTree when I select ANY item under the main report name. That is, if I select PrinterName2, then the SelectedTreeItem will be the main viewmodel for ReportName1.
What I need to know is, how can I tell that PrinterName2 is selected as opposed to PrinterName1?
My eventual goal is to allow selection of any leaf or branch in the tree and remove only that selected leaf or branch.
Is there a way to do this?
Thanks for any help on this.
Here is one option to solve this using a simple DataTemplate for the TreeView which contains a MouseBinding to call a select command on the parent ViewModel and pass the clicked item as a CommandParameter.
If your ViewModel looks something like this:
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
public ItemViewModel SelectedItem { get; set; }
public ICommand SelectItem { get; private set; }
public MainViewModel()
{
SelectItem = new LazyCommand<ItemViewModel>(ExecuteSelect);
Items = new ObservableCollection<ItemViewModel>();
}
private void ExecuteSelect(ItemViewModel item)
{
SelectedItem = item;
}
}
with a straightforward ViewModel for the items:
public class ItemViewModel
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
public string Name { get; set; }
public ItemViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
}
Then you can define a TreeView with an HierarchicalDataTemplate as ItemTemplate:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}" >
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</TextBlock.InputBindings>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The crucial part is the binding to the TreeViews DataContext.SelectItemCommand from the MouseBinding of each item and passing the item in as a parameter. You can then handle the selection itself (setting SelectedItem, etc.) in the ViewModel.

WPF: How to bind object to ComboBox

Trying to learn how to bind objects to various types of controls. In this instance, I want to get sample data in my object to appear in ComboBox. The code runs but what appears instead of values (David, Helen, Joe) is text "TheProtect.UserControls.Client")
XAML: (ucDataBindingObject.xaml)
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ComboBox Width="310"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
this.DataContext = this;
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
There are several ways to tell the framework what to display
1) Use DisplayMemberPath on the ComboBox (this will display the named property):
<ComboBox ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name"
/>
2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display, rather than just a property:
<ComboBox ItemsSource="{Binding Path=Clients}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
3) Add a ToString() override to source class. Useful if you always want to display the same string for a given class. (Note that the default ToString() is just the class type name, which is why you see "TheProtect.UserControls.Client".)
public class Client
{
// ...
public override string ToString()
{
return string.Format("{0} ({1})", Name, ID);
}
}
4) Add a DataTemplate to the XAML resources. This is useful for associating a given class type with a more complex or stylized template.
<UserControl xmlns:local="clr-namespace:TheProject.UserControls">
<UserControl.Resources>
<DataTemplate DataType="local:Client">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
// ...
</UserControl>
In DisplayMemberPath, give the name of the property which you want to show in the comboBox. In SelectedValuePath, give the name of the property which you want to select. When you do a ComboBox.SelectedValue, you will get the value of this property.
Trying to get selected value from combobox returns System.Data.Entity.DynamicProxies.x
private void Button_Click(object sender, RoutedEventArgs e){
string _scanner0 = int.Parse(mycmb.SelectedValue.ToString());
string _scanner1 = mycbr.SelectedItem.ToString();
string _scanner2 = mycbr.SelectedValuePath.ToString();
string _scanner3 = mycbr.text.ToString();
}
all these Returns System.Data.Entity.DynamicProxies.x
What should i do?

wpf treeview mvvm

I am trying to populate a treeview using mvvm but the tree does not display any data.
I have a Employee list which is a property in my vm which contains he employee data.
the xaml is as follows.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding EmpList}" >
<TextBlock Text="{Binding EmpName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Is there anything i am missing here.
thanks
Hi Ian's suggested article indeed is a great read!
The trick is that you should specify how the Treeview shows its items through type specific (Hierarchical)DataTemplates. You specify these datatemplates in the Treeview's resources (or higher up the visual tree if you want to reuse them in more treeviews).
I tried to simulate what you want:
<Window x:Class="TreeViewSelection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewSelection"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TreeView ItemsSource="{Binding Enterprises}">
<TreeView.Resources>
<!-- template for showing the Enterprise's properties
the ItemsSource specifies what the next nested level's
datasource is -->
<HierarchicalDataTemplate DataType="{x:Type local:Enterprise}"
ItemsSource="{Binding EmpList}">
<Label Content="{Binding EntName}"/>
</HierarchicalDataTemplate>
<!-- the template for showing the Employee's properties-->
<DataTemplate DataType="{x:Type local:Employee}">
<Label Content="{Binding EmpName}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TreeViewSelection
{
public partial class Window1 : Window
{
public ObservableCollection<Enterprise> Enterprises { get; set; }
public Window1()
{
InitializeComponent();
Enterprises = new ObservableCollection<Enterprise>
{
new Enterprise("Sweets4Free"),
new Enterprise("Tires4Ever")
};
DataContext = this;
}
}
public class Enterprise : DependencyObject
{
public string EntName { get; set; }
public ObservableCollection<Employee> EmpList { get; set; }
public Enterprise(string name)
{
EntName = name;
EmpList = new ObservableCollection<Employee>
{
new Employee("John Doe"),
new Employee("Sylvia Smith")
};
}
}
public class Employee : DependencyObject
{
public string EmpName { get; set; }
public Employee(string name)
{
EmpName = name;
}
}
}
Check out Josh Smith's article on exactly this topic... Helped me no end!
p.s. Looks like you're missing the DataType property on the HierarchicalDataTemplate, e.g.
<HierarchicalDataTemplate
DataType="{x:Type local:ItemType}"
ItemsSource="{Binding ...}" >

Resources