I know a way to do MVC binding of one string to one TextBox. That's how it can be done:
C#:
namespace WpfApplication4
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Model;
}
public ModelClass Model = new ModelClass();
private void button1_Click(object sender, RoutedEventArgs e)
{
Model.Output += "Setting New Output! ";
}
public class ModelClass : INotifyPropertyChanged
{
string _output;
public event PropertyChangedEventHandler PropertyChanged =
delegate { };
public string Output
{
get { return _output; }
set { _output = value;
PropertyChanged(this,
new PropertyChangedEventArgs("Output"));
}
}
}
}
}
XAML:
<Window x:Class="WpfApplication4.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>
<Button Content="Button" VerticalAlignment="Top"
Name="button1" Click="button1_Click" />
<TextBox VerticalAlignment="Bottom"
Name="textBox1" Text="{Binding Path=Output}" />
</Grid>
</Window>
But I can't find a way to bind a two-dimensional array (or List) to a Grid or DataGrid. Can you help me with it? I couldn't find a working example on SO.
Consider using a DataGrid to display your two-dimensional array, assuming you can store your data as a List<ColumnData> where ColumnData is a class with one property per table column.
The WPF SDK contains a DataGrid, and there are several data grids from vendors available that have additional features.
if you wanna bind data to a datagrid you should be read something about the following.
ICollectionView, BindingListCollectionView
if you have somekind of collection you simply set the itemssource.
<DataGrid ItemsSource="{Binding Path=MyCollection, Mode=OneWay}" />
Collection types are mostly ObservableCollection or DataSet/DataTable. if your collection supports editing and so on, you can do it with the datagrid.
Related
I'm trying to bind an ObservableCollection sitting in my ViewModel class to a ListView that uses a GridView for displaying rows but I am unable to show my data in the list.
For testing purposes I put a TextBlock and can successfully show the data with Binding (however only if I do DataContext = ViewModel in code behind first, doing ViewModel.MyData in XAML does not work)
I've noticed that whatever ItemsSource is bound to, there is always a random number of empty element showing in the list, I can tell that since when I hover my mouse on the ListView the rows highlight. This number does not match the capacity of my collection.
If in code behind I manually set the ItemsSource to the collection I want to display, the correct number of element then shows up but there are still no data displayed.
XAML
<Page x:Class="GestionStockWPF.MainPage"
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:GestionStockWPF"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="MainPage"
ShowsNavigationUI="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="13*"/>
<RowDefinition Height="77*"/>
</Grid.RowDefinitions>
<!-- The Binding here seems to do nothing, there isn't the correct amount of rows in the app -->
<ListView x:Name="listView"
HorizontalAlignment="Stretch"
Grid.Row="1" Margin="30, 0, 30, 30"
ItemsSource="{Binding Source=Tests}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=A}"
Header="Asset Number" Width="200"/>
</GridView>
</ListView.View>
</ListView>
<!-- This textblock is just here for testing purposes, it successfully shows "A" -->
<TextBlock x:Name="textBlock" Text="{Binding Path=Tests[0].A}"
HorizontalAlignment="Left" Margin="252,28,0,0" TextWrapping="Wrap"
VerticalAlignment="Top"/>
</Grid>
</Page>
Code Behind File
using System.Windows;
using System.Windows.Controls;
namespace GestionStockWPF
{
public partial class MainPage : Page
{
public MainPageViewModel ViewModel;
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
// If I do DataContext = this;
// and then set the binding of the TextBlock to "ViewModel.Tests[0].A" nothing is shown
DataContext = ViewModel;
// If I don't do that the listview does not contain the correct number of rows
listView.ItemsSource = ViewModel.tests;
}
}
}
ViewModel & Test class
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace GestionStockWPF
{
// Not sure if I have to implement INotifyPropertyChanged but my real class does it
public class Test : INotifyPropertyChanged
{
public string A { get { return "A"; } }
public string B { get { return "B"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class MainPageViewModel
{
public ObservableCollection<Test> Tests { get; set; }
public MainPageViewModel()
{
tests = new ObservableCollection<Test>();
for (int i = 0; i < 50; ++i)
{
Tests.Add(new Test());
}
}
}
}
Your binding is incorrect.
It should be:
ItemsSource="{Binding tests}"
You need to specify the path in the binding, not the binding source.
The explicit Path part is optional in a binding, so you also can change your text block binding to:
Text="{Binding tests[0].A}"
By the way, the properties should use PascalCase, so better call it Tests.
I am trying to using mvvm pattern with wpf to create an interface for a project previously did in win form.
In this project i have an object that contains some List<> that i have to show in real time on my interface with a combobox, the problem is that combobox don't change his values. I'm using the dll of mvvm fundation for implement NotifyPropertyChanged. I think to make some mistake but i don'y know where is it.
I've tried to do a simple code with only one list in viewmodel and without a model but the result doesn't change.
<Window x:Class="ProvaComboBox.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:ProvaComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.DataContext>
<local:ViewModel />
</Grid.DataContext>
<Button Content="Generate" Command="{Binding Generate}"/>
<Button Content="Clear" Command="{Binding Clear}"/>
<ComboBox ItemsSource="{Binding Path=Word, Mode=OneWay}" />
</Grid>
</Window>
//view Model
class ViewModel:ObservableObject
{
private List<string> _word;
public List<string> Word
{
get { return _word; }
}
public ViewModel()
{
_word = new List<string>();
}
public ICommand Generate
{ get { return new RelayCommand(GenerateExecute); } }
void GenerateExecute()
{
_prova.Add("pippo");
_prova.Add("pluto");
RaisePropertyChanged("Word");
}
public ICommand Clear
{ get { return new RelayCommand(ClearExecute); } }
void ClearExecute()
{
_prova.Clear();
RaisePropertyChanged("Word");
}
}
//View:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
I think that the problem it's RaisePropertyChanged, but it work correctly with normal variables.
I've tryed also using ObservableCollection and it work, but i can't use it with real project.
(p.s. Its my first question in stack overflow, sorry if i did some mistake!)
use ObservableCollection like that
public ObservableCollection<string> Word
{
get => _word;
set
{
_word= value;
RaisePropertyChanged("Word");
}
}
and change the binding mode in your combobox xaml code from OneWay to TwoWay or just remove it to be something like
<ComboBox ItemsSource="{Binding Path=Word}" />
I can't seem to bind a control's value to an object. I want to bind a TextBox to a string object, the idea is that when textbox's text changes, it should automatically change the object as well. couldn't figure out what I'm doing wrong. Here is what I have tried:
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
string str;
public MainWindow()
{
InitializeComponent();
this.DataContext = str;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
and MainWindow.xaml:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="150">
<Grid Margin="0,0,642,319">
<TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="{Binding str}" VerticalAlignment="Top" Width="120" Margin="0,0,-120,-46" />
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click" Height="23" Margin="0,28,-75,-51" RenderTransformOrigin="0.423,2.257" />
</Grid>
</Window>
So, when I enter something to the textbox and click the button, I should see the text in str while debugging but it is always null
Change the str to a auto property:
public string str { get; set; }
Change the DataContext to:
DataContext = this;
The DataContext is the class which will hold your binding properties/commands/events.
The properties/commands/events need to be public in order to be accessible by your view.
For the two-way binding to work, you have to notify to the UI binding that the property has been changed and for that you need to implement the INotifyPropertyChanged interface for the class which holds the properties which have been bound in the UI. You will need a private property and you cannot notify from a auto-property.
Simple Example:
public class Sample : INotifyPropertyChanged
{
private string _str;
public string Str
{
get { return _str; }
set
{
_str = value;
NotifyPropertyChanged(nameof(Str));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
First, data bindings in WPF only work with public properties. So you must explicitely declare one in your code behind (instead of string str;)
public string str { get; set; }
Second, the DataContext property of a view defines the object / class in which the property will be searched for the bindings. The line this.DataContext = str; in your example means that you want your bindings in the view to be looked for inside the str object (which is a string). You should replace this line by
this.DataContext = this;
so that the bindings will be searched inside the code behind of this view itself.
Remark
You could also stay with the line this.DataContext = str; if str is a public property and bind using an expression such as
<TextBox Text="{Binding .}" />
which will bind to the value of the DataContext property.
Maybe you can use MVVM light to do the binding.
I am coding an application, its a quiz, I have a main Window where I load different UserControls (Pages). so my problem is that I have one image on the MainWindow, I want to change the Visibility of this image from Collapsed to Visible from one of the UserControls but with no luck...
Here is my MainWindow:
<Window x:Class="MuseonQuiz_v3.PageSwitcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:MuseonQuiz_v3.Pages"
xmlns:k="http://schemas.microsoft.com/kinect/2013"
Title="MainWindow" Height="710" Width="1127" IsEnabled="True" DataContext="{Binding}" FontFamily="KaiTi" ResizeMode="NoResize" WindowStyle="None"
WindowStartupLocation="CenterScreen" WindowState="Maximized">
<Grid>
<Grid>
<k:KinectRegion Name="kinectRegion">
<ContentControl x:Name="mainContentControl"/>
</k:KinectRegion>
</Grid>
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Grid.Resources>
<k:KinectSensorChooserUI HorizontalAlignment="Center" VerticalAlignment="Top" Name="sensorChooserUi" />
<k:KinectUserViewer VerticalAlignment="Bottom" HorizontalAlignment="Center" k:KinectRegion.KinectRegion="{Binding ElementName=kinectRegion}" Height="600" Width="600" />
<Image Name="colorStreamImage" Width="640" Height="480" Visibility="Collapsed" HorizontalAlignment="Center" />
</Grid>
</Grid>
and this is my UserControl:
public partial class Selectie : UserControl, ISwitchable
{
string backgroundSelectie = "pack://application:,,,/MuseonQuiz_v3;component/Images/Selectie/selectie_background.jpg";
public Selectie()
{
InitializeComponent();
selectieBackground();
animatieButtons();
}
#region ISwitchable Members
public void UtilizeState(object state)
{
throw new NotImplementedException();
}
#endregion
}
My question is... how do I change the Visibility of the colorStreamImage that is located in the MainWindow from the UserControl... I have tried making an instance of the MainWindow, but that does not work, maybe I have to use some binding, but I am not sure, I appreciate any help you can provide!
As Clemens mentioned, your best bet is to go down the MVVM path. This is a good tutorial to get started In the Box – MVVM Training.
First, you can create a view model that implements INotifyPropertyChanged. In this case, you may want it to have at least one property of type Visibility.
public class MainViewModel : INotifyPropertyChanged
{
private Visibility _imageVisibility;
public Visibility ImageVisibility
{
get { return _imageVisibility; }
set { _imageVisibility = value; OnPropertyChanged("ImageVisibility"); }
}
private BitmapImage _imageSource;
public BitmapImage ImageSource{...}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler eventHandler = PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you'll want to set this view model as the data context on the main window. To do this, Paul Stovell has a good post on the different approaches: http://paulstovell.com/blog/mvvm-instantiation-approaches. Once we set it on the main window, the Selectie element will inherit the data context. Using the simplest approach:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Your Image element might then bind to the property like this:
<Image Visibility="{Binding ImageVisibility, UpdateSourceTrigger=PropertyChanged}" Source="{Binding ImageSource}" Height="200" Width="200"></Image>
The Selectie element can now change the ImageVisbility property on the view model since it shares the same data context as MainWindow. (I used the code-behind as an example. You'll probably want to push as much of that logic out of the view and into the view model or further downstream)
public partial class Selectie : UserControl
{
public Selectie()
{
InitializeComponent();
}
private void Selectie_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (((MainViewModel)this.DataContext).ImageVisibility == System.Windows.Visibility.Visible)
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Collapsed;
else
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Visible;
}
}
I'm still pretty new to WPF and I decided to change the application I am developing to start following the MVVM pattern as best as I could. I am running into a problem when I try to have a list box dictate the view model of a content control. I've been stuck on this for a while and searching the internet is not producing answers for me.
For some reason a new instance of the view model the list box contains is being generated as the data context of the content control. When I was debugging I made sure that the list box contains the view models it should, and that the item I select on the list box is indeed the item that the list box is selecting, however the content control changing based on the selection. There is a view model populating the content control, however it is not in the collection the list box populates from. And I can somehow delete the view model in the content control via my remove button. But when I make a selection change on the list box, or add a new item to the collection it populates the content control with a new view model that once again is not in the collection. I have no clue why it is doing this, or what in my code would suggest this behavior.
I made a simple application to try and figure out what I'm doing wrong. It replicates my problem perfectly. I'm pretty sure the buttons don't adhere to MVVVM (supposed to run a command contained in the view model to adhere to MVVM from what I've been reading) but that is not my main concern right now as the problem exists without the buttons.
MainWindow.xml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="440" Width="436">
<Window.DataContext>
<local:mwvm/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ucvm}">
<local:uc/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Content="a" HorizontalAlignment="Left" Margin="323,351,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click"/>
<Button Content="r" HorizontalAlignment="Left" Margin="323,378,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click_1"/>
<ContentControl Margin="10,10,110,10" Content="{Binding SelectedItem, ElementName=lb_UCs}"/>
<ListBox x:Name="lb_UCs" HorizontalAlignment="Left" Height="336" Margin="323,10,0,0" VerticalAlignment="Top" Width="95" ItemsSource="{Binding UCs}" DisplayMemberPath="CoolText"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class PanelPartsView : UserControl
{
private PanelPartsViewModel _DC;
public PanelPartsView()
{
InitializeComponent();
_DC = DataContext as PanelPartsViewModel;
}
private void btn_Remove_Click(object sender, RoutedEventArgs e)
{
_DC.Panels.Remove(lb_Panels.SelectedItem as PartsViewModel);
}
private void btn_Add_Click(object sender, RoutedEventArgs e)
{
var pvm = new PartsViewModel();
_DC.Panels.Add(pvm);
lb_Panels.SelectedItem = pvm;
System.Console.WriteLine("lb_Panels.selecteditem = {0}", ((PartsViewModel)lb_Panels.SelectedItem).PanelName);
System.Console.WriteLine("cc_PanelParts.content = {0}", ((PartsViewModel)cc_PanelParts.Content).PanelName);
}
}
mwvm
class mwvm
{
private ObservableCollection<ucvm> _UCs = new ObservableCollection<ucvm>();
public ObservableCollection<ucvm> UCs
{
get { return _UCs; }
}
public mwvm()
{
//this is for for testing, the real application would be purely dynamic
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
}
}
uc.xaml
<UserControl
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:WpfApplication1" x:Class="WpfApplication1.uc"
mc:Ignorable="d" d:DesignWidth="300" Height="90">
<Grid>
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
<Button Content="{Binding CoolText}" Margin="10,10,10,0" Height="44" VerticalAlignment="Top"/>
<TextBox Height="23" Margin="10,59,10,0" TextWrapping="Wrap" Text="{Binding CoolText}" VerticalAlignment="Top"/>
</Grid>
</UserControl>
uc.xaml.cs
public partial class uc : UserControl
{
public uc()
{
InitializeComponent();
}
}
ucvm.cs
class ucvm : INotifyPropertyChanged
{
private static int i = 1;
private string _CoolText = "<" + i++ + ">" + System.DateTime.Now.ToLongTimeString();
public string CoolText
{
get { return _CoolText; }
set
{
_CoolText = value;
NPC("CoolText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NPC(string s)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
I have also tried binding the content control like so...
<ContentControl Content="{Binding SelectedUCVMl, Mode=OneWay}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" SelectedItem="{Binding SelectedUCVM}" DisplayMemberPath="CoolText"/>
...and so...
<ContentControl Content="{Binding UCs/}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="CoolText"/>
but to no avail.
Any help would be greatly appreciated.
It looks like you just have to remove this part from uc.xaml:
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
This syntax creates a new instance of the view model, each time an instance of uc.xaml is created, which of course isn't what you want. You want the data context of uc.xaml instances to inherit the instance currently selected in the list box.