Dynamically Adding TextBox using a Button within MVVM framework - c#

I keep climbing the steep WPF hill! So I want to create a UI that allows the user to dynamically add a text box. To do this they would hit a button.
I've managed to create this using code behind but I want to move towards an MVVM structure so I don't have any code in the view. I've tried ICommand and ObservableCollection but I'm missing something and I don't know where. Here is my simple example.
XAML: Very basic with one button that adds a row.
<Window x:Class="WPFpractice072514.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFpractice072514"
Title="MainWindow" Height="350" Width="525">
<Grid Name="mymy" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0" Name="ButtonUpdateArtist"
Content="Add TextBox" Click="ButtonAddTexboxBlockExecute" />
</Grid>
</Window>
C# Code Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPFpractice072514
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region members
int count = 0;
#endregion
public MainWindow()
{
InitializeComponent();
}
private void ButtonAddTexboxBlockExecute(Object Sender, RoutedEventArgs e)
{
TextBox t = new TextBox();
t.Height = 20;
t.Width = 20;
t.Name = "button";
RowDefinition rowDef1;
rowDef1 = new RowDefinition();
mymy.RowDefinitions.Add(rowDef1);
ColumnDefinition colDef1;
colDef1 = new ColumnDefinition();
mymy.ColumnDefinitions.Add(colDef1);
++count;
mymy.Children.Add(t);
Grid.SetColumn(t, 1);
Grid.SetRow(t, count);
}
}
}
Questions: What code (XAML and C#) do I need to be able to move the method out of the code behind and into a viewmodel?
Can you use commands to dynamically add a textbox?
I'm assuming that the textboxes must be kept in a container which in this case is what grid is for. But if I'm using an MVVM do I need to contain the textboxes in a listview or some other container that uses ItemsSource?

Follow these steps and you are done:
Use ItemsControl and bind it's ItemsSource to some collection (preferably ObservableCollection) in your ViewModel.
Define ItemTemplate for ItemsControl with TextBox in it.
Create an ICommand in ViewModel and bind it to button.
On command execute add item in the collection and you will see TextBox gets added automatically.
XAML:
<StackPanel>
<Button Content="Add TextBox" Command="{Binding TestCommand}"/>
<ItemsControl ItemsSource="{Binding SomeCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> SomeCollection { get; set; }
public ICommand TestCommand { get; private set; }
public MainWindowViewModel()
{
SomeCollection = new ObservableCollection<string>();
TestCommand = new RelayCommand<object>(CommandMethod);
}
private void CommandMethod(object parameter)
{
SomeCollection.Add("Some dummy string");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
RelayCommand:
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
Note - I assume you know how to plug View with your ViewModel by setting DataContext to make the binding magic to work.

[link][1]
class TestViewModel : BindableBase
{
private TestModel testModel;
public ICommand AddCommand { get; private set; }
public TestViewModel(StackPanel stkpnlDynamicControls)
{
testModel = new TestModel();
TestModel.stkPanel = stkpnlDynamicControls;
AddCommand = new DelegateCommand(AddMethod);
}
public TestModel TestModel
{
get { return testModel; }
set { SetProperty(ref testModel, value); }
}
private void AddMethod()
{
Label lblDynamic = new Label()
{
Content = "This is Dynamic Label"
};
TestModel.stkPanel.Children.Add(lblDynamic);
}
}

Related

C# Databinding Textblock, PropertyChanged stays null

Im trying to change 2 textblocks with data binding. The propertyChanged is always null, so the ui wont update.
This is my model code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MovieApp.Models
{
public class MovieModel : INotifyPropertyChanged
{
string original_title, overview;
public event PropertyChangedEventHandler PropertyChanged;
public string Original_Title {
get
{
return original_title;
}
set
{
original_title = value;
onPropertyChanged(nameof(Original_Title));
}
}
public string Overview
{
get
{
return overview;
}
set
{
overview = value;
onPropertyChanged(nameof(Overview));
}
}
protected void onPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The mainview.xaml.cs:
using MovieApp.API;
using MovieApp.Models;
using MovieApp.Processor;
using System.Windows;
namespace MovieApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//private readonly MovieModel movieModel = new MovieModel();
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
// DataContext = movieModel;
}
private async void previousImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 484718;
await MovieProcessor.LoadMovie(id);
}
private async void nextImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 527774;
await MovieProcessor.LoadMovie(id);
}
}
}
and the maindwindow.xaml:
<Window x:Class="MovieApp.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:MovieApp.Models"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MovieModel x:Key="movieModel" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Click="previousImageButton_Click">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Click="nextImageButton_Click">Next</Button>
</Grid>
</Window>
EDIT:
Added the movieprocessor code:
using MovieApp.API;
using MovieApp.Models;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace MovieApp.Processor
{
class MovieProcessor
{
public static async Task<MovieModel> LoadMovie(int id)
{
string url = $"movie/{id}?api_key=77e7d2ef687aedca2119680778f1d619&language=en-US";
using (HttpResponseMessage response = await ApiCaller.httpClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
MovieModel movie = await response.Content.ReadAsAsync<MovieModel>();
Console.WriteLine(movie.Original_Title);
Console.WriteLine(movie.Overview);
return movie;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
}
I have no idea what could be wrong. I tried multiple things but nothing seemed to work for me. I tried adding datacontext but that didnt work either. I let it commented in my code so anyone can see it.
If your MovieProcess class sets the value of MovieModel.Original_Title and MovieModel.Overview property then you have to ensure that MovieProcess is accessing the same instance of MovieModel as your view (xaml).
Instead of using StaticResource movieModel assing DataContext in code behind.
private readonly MovieModel movieModel = new MovieModel();
public MovieProcessor MovieProcessor { get; set; }
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
DataContext = movieModel;
MovieProcessor = new MoviewProcessor(moviewModel);
}
Xaml
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Original_Title}" />
<TextBlock Text="{Binding Overview }" />
</StackPanel>
MovieProcessor class
public class MovieProcessor
{
private readonly MovieModel movieModel;
public MovieProcessor(MovieModel movieModel)
{
this.movieModel = movieModel;
}
public async Task LoadMovie(int id)
{
...
movieModel.Original_Title = <loaded_movie_title>;
movieModel.Overview = <loaded_movie_overview>;
...
}
}
I bind the command to the Buttons in the MovieProcessor to show data in Maindwindow with StaticResource movieModel, below is my code:
NotifyObject.cs
public class NotifyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MyCommand.cs
public class MyCommand : ICommand
{
private Func<object, bool> _canExecute;
private Action<object> _execute;
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
if (_execute != null && CanExecute(parameter))
{
_execute(parameter);
}
}
public MyCommand(Action<object> execute) : this(execute, null)
{
}
public MyCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
}
MovieProcessor.cs
public class MovieProcessor:NotifyObject
{
private MovieModel vm;
public MovieModel VM
{
get { return vm; }
set
{
vm = value;
OnPropertyChange("VM");
}
}
public MovieModel LoadMovie(int id)
{
//....
}
private MyCommand _cmd1;
public MyCommand Cmd1
{
get
{
if (_cmd1 == null)
_cmd1 = new MyCommand(new Action<object>
(
o =>
{
int id = 484718;
LoadMovie(id);
}
));
return _cmd1;
}
}
private MyCommand _cmd2;
public MyCommand Cmd2
{
get
{
if (_cmd2 == null)
_cmd2 = new MyCommand(new Action<object>
(
o =>
{
int id = 527774;
LoadMovie(id);
}
));
return _cmd2;
}
}
}
MainWindow.xaml
<Window.Resources>
<local:MovieProcessor x:Key="movieModel" />
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd1}">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd2}">Next</Button>
</Grid>

Change WPF DataGrid column cell background color based on values

I am trying to fill cells with color in certain column. Column name is "NRO" and I want to fill cells staring with 2 yellow color and cells starting with 3 blue color. I went through answer given here: Change DataGrid cell colour based on values
Also tried several other approaches but can't get any of them work. I also don't understand how to implement any of them in my setup. They all seems to have <DataGridTextColumn Binding="{Binding Name}">. What it should be in my case?
Here is my XAML:
<Window x:Class="DB_inspector_FilterTest.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"
mc:Ignorable="d"
Title="DB database inspector v.0.0.01" Height="600" Width="1000" Icon="logo_icon-small.jpg" Background="White">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="DataGrid1" Margin="0,103,0,0" Background="White" BorderBrush="#FF38853F"/>
<TextBox x:Name="NameSearch" HorizontalAlignment="Left" Height="20" Margin="22,41,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="437" TextChanged="NameSearch_TextChanged"/>
<Button Content="Load" Margin="640,41,0,0" Click="Button_Click_1" BorderBrush="{x:Null}" Foreground="White" Background="#FF55B432" Width="66" Height="29" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="11" VerticalAlignment="Top" Width="992" BorderBrush="{x:Null}" Background="{x:Null}"/>
<Label Content="Customer name" HorizontalAlignment="Left" Height="25" Margin="22,11,0,0" VerticalAlignment="Top" Width="154"/>
<CheckBox x:Name="ActiveCustomer" Content="Active" HorizontalAlignment="Left" Height="24" Margin="486,63,0,0" VerticalAlignment="Top" Width="86" Click="ActiveCustomer_Click_1"/>
<CheckBox x:Name="Only" Content="Leave only good" HorizontalAlignment="Left" Height="17" Margin="486,41,0,0" VerticalAlignment="Top" Width="115" Click="CheckBox_Click"/>
<Image Margin="856,0,22,520" VerticalAlignment="Bottom" Source="logo_small.jpg" Height="27"/>
</Grid>
</Window>
ADDITION:
If anybody have time, while I will be trying to figure it out myself, give me some hints how to proceed with my application, here is my full code:
using System.Data.Odbc;
using System.Windows;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace DB_inspector_FilterTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
ProgressBar.IsIndeterminate = true;
DataGrid1.ItemsSource = await GetDataAsync();
ProgressBar.IsIndeterminate = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private Task<DataView> GetDataAsync()
{
return Task.Run(() =>
{
string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSSE;Uid=ADMIN;Pwd=123;";
string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSFI;Uid=ADMIN;Pwd=123;";
string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
DataTable dataTable = new DataTable("COMPANY");
// using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
{
dbConnectionDE.Open();
OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);
dadapterDE.Fill(dataTable);
}
using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
{
dbConnectionFR.Open();
OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);
var newTable = new DataTable("COMPANY");
dadapterFR.Fill(newTable);
dataTable.Merge(newTable);
}
return dataTable.DefaultView;
});
}
private Dictionary<string, string> _conditions = new Dictionary<string, string>();
private void UpdateFilter()
{
try
{
var activeConditions = _conditions.Where(c => c.Value != null).Select(c => "(" + c.Value + ")");
DataView dv = DataGrid1.ItemsSource as DataView;
dv.RowFilter = string.Join(" AND ", activeConditions);
}
catch (Exception)
{
//MessageBox.Show(ex.Message);
}
}
private void NameSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
string filter = NameSearch.Text;
if (string.IsNullOrEmpty(filter))
_conditions["name"] = null;
else
_conditions["name"] = string.Format("NAME Like '%{0}%'", filter);
UpdateFilter();
}
private void ActiveCustomer_Click_1(object sender, RoutedEventArgs e)
{
if (ActiveCustomer.IsChecked == true)
{
_conditions["active"] = string.Format("ACTIVE Like '%{0}%'", "1");
UpdateFilter();
}
else
{
_conditions["active"] = null;
UpdateFilter();
}
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
if (OnlyFIandSE.IsChecked == true)
{
_conditions["onlyfrandde"] = string.Format("NRO Like '2%' OR NRO Like '3%'");
UpdateFilter();
}
else
{
_conditions["onlyfrandde"] = null;
UpdateFilter();
}
}
}
}
Things I don't understand at least now: How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?
ATTEMPT 3:
Here is my latest MVVM attempt.
C#:
using System;
using System.ComponentModel;
using System.Data;
using System.Data.Odbc;
using System.Windows;
using System.Windows.Input;
namespace DB_inspector
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
}
public class ViewModel : INotifyPropertyChanged
{
public ICommand myCommand => new RelayCommand(obj =>
{
try
{
string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSSE;Uid=ADMIN;Pwd=123;";
string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSFI;Uid=ADMIN;Pwd=123;";
string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
DataTable dataTable = new DataTable("COMPANY");
// using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
{
dbConnectionDE.Open();
OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);
dadapterDE.Fill(dataTable);
}
using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
{
dbConnectionFR.Open();
OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);
var newTable = new DataTable("COMPANY");
dadapterFR.Fill(newTable);
dataTable.Merge(newTable);
}
_ = dataTable.DefaultView;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
});
private bool _allowUIChanges = true;
public bool AllowUIChanges
{
get => _allowUIChanges;
set
{
_allowUIChanges = value;
OnPropertyChanged(nameof(AllowUIChanges));
OnPropertyChanged(nameof(IsReadOnlyDataGrid));
}
}
private void OnPropertyChanged(string v)
{
throw new NotImplementedException();
}
public bool IsReadOnlyDataGrid
{
get => !_allowUIChanges;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
}
XAML:
<Window x:Class="DB_inspector.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"
mc:Ignorable="d"
Title="DB database inspector" Height="595.404" Width="1005.571">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}" Width="998" Margin="0,98,0,0" >
</DataGrid>
<Image Height="41" Margin="0,21,10,0" Width="141" Source="logo_small.jpg" HorizontalAlignment="Right" VerticalAlignment="Top"/>
<Button Content="Go" Command="{Binding myCommand}" Width="80" Height="30" Margin="48,42,870,492"/>
</Grid>
</Window>
Any suggestions what is still wrong here? No errors, but button does not process anything.
I suggest IValueConverter or IMultiValueConverter binding for Cell's Background property.
I'm not sure how it works with autogenerated columns set but with manual it looks like this. I'm providing here not a working copy but a markup example.
XAML
<Window.Resources>
<local:MyCellBackgroundConverter x:Key="myCellBackgroundConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<!-- some your markup here -->
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
<DataGridTextColumn Header="Column1" Binding="{Binding Value1}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource myCellBackgroundConverter}">
<Binding Path="Value1"/>
<Binding Path="Value2"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Column2" Binding="{Binding Value2}"/>
</DataGrid>
</Grid>
The ViewModel Class
using System.Collections.ObjectModel;
using System.ComponentModel;
// ...
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyItem> _myCollection = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
OnPropertyChanged(nameof(MyCollection));
}
}
public ViewModel()
// you may load or add the data to MyCollection here
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Item
using System.ComponentModel;
// ...
public class MyItem : INotifyPropertyChanged
{
private string _value1 = string.Empty;
private string _value2 = string.Empty;
public string Value1
{
get => _value1;
set { _value1 = value; OnPropertyChanged(nameof(Value1)); }
}
public string Value2
{
get => _value2;
set { _value2 = value; OnPropertyChanged(nameof(Value2)); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And finally the Converter
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
//...
public class MyCellBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is string value1 && values[1] is string value2)
{
if (value1.Length > 0)
{
return Brushes.Green;
}
else
if (value2.Length > 0)
{
return Brushes.Yellow;
}
else
return Brushes.Red;
}
else return Brushes.White;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;
}
In alternative way you may use Style.DataTriggers directly in XAML.
For additional information about bindings and properties look for MVVM programming pattern. In short you're not need x:Name anymore because in MVVM pattern you interacting only with ViewModel class data instances and can't interact with contols directly there (and that's fine). Meanwhile Contols automatically sync with data binded to them. Calling OnPropertyChanged("PropertyName") here simply cause the GUI refresh.
In relation to markup of your XAML example, try wrapping the Control groups in StackPanel and learn about it. It will save your time spent fighting with margins. Simply set few colums and/or rows in Window's Grid and place StackPanels there assigning Grid.Column and Grid.Row to them.
ADDITION:
How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?
ObservableCollection<> is same as List<> and you may use it in the same way. The difference that first one implements CollectionChanged event that notifies DataGrid if any items was added or removed from the collection.
Your Button.Click event handler contains redundant async/await declaration.
Let's move forward and see how it can be done with MVVM.
XAML
<Button Content="Go" Command="{Binding myCommand}"/>
Command must implement ICommand interface. You have to do 2 things for proper implementation:
1) Add RelayCommand separate Class implementing ICommand interface
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
2) Add the Command instance to your ViewModel class
public ICommand myCommand => new RelayCommand(obj =>
{
// do the same here as in Button.Click above
});
Next, you may need some blocking UI thing that will prevent user from any actions while data is loading.
ViewModel
private bool _allowUIChanges = true;
public bool AllowUIChanges
{
get => _allowUIChanges;
set
{
_allowUIChanges = value;
OnPropertyChanged(nameof(AllowUIChanges));
OnPropertyChanged(nameof(IsReadOnlyDataGrid));
}
}
public bool IsReadOnlyDataGrid
{
get => !_allowUIChanges;
}
Finally bind your Control properties to it
XAML
<Button Content="Go" Command="{Binding myCommand}" Enabled="{Binding AllowUIChanges}"/>
<DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
Then set AllowUIChanges to false while data is loading.

ComboBox does not update when ItemsSource changes

I hav a ComboBox that is bound to a List<string>. When the List changes, the ComboBox does not, even though PropertyChanged was raised. When debugging, I found out that the List Property is even read.
The error can be reproduced using the following code:
XAML
<Window x:Class="ComboBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="90" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Source, Mode=OneWay}"/>
<Button Grid.Column="1" Content="add string" Command="{Binding}" CommandParameter="Add"/>
</Grid>
</Window>
Code behind
using System.Windows;
namespace ComboBoxTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
ViewModel
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace ComboBoxTest
{
class ViewModel : INotifyPropertyChanged, ICommand
{
public ViewModel()
{
Source = new List<string>();
Source.Add("Test1");
Source.Add("Test2");
Source.Add("Test3");
}
private List<string> _Source;
public List<string> Source
{
get { return _Source; }
set
{
_Source = value;
OnPropertyChanged("Source");
}
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExecute(object parameter)
{
return true;
}
public event System.EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if ((string)parameter == "Add")
{
Source.Add("New string");
OnPropertyChanged("Source");
}
}
}
}
Why isn't the ComboBox updating?
The ComboBox does not update because it doesn't see any changes when it checks the List. The reference stays the same and the ComboBox is not informed about changes inside the List.
Refactoring the code to use ObservableCollection instead of List will solve the problem, because ObservableCollection implements INotifyCollectionChanged, what is necessary to inform the View about Changes inside an Object.

Windows Phone 8.1 - Menu flyout item click command c#

I am writing an application for Windows Phone 8.1, and I wanted to use a flyout on listView item. Because I am doing my best to write nice app, I am trying to use MVVM pattern and resource dictionaries with templates insead of all xaml in one page.
However, I can't bind my MenuFlyoutItem Command - it seems like it doesn't see the datacontext of the page, or it has some other dataContext. Here is some code:
1) My template in a separate resource dictionary:
<Grid Margin="0, 0, 0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:EmptyDateConverter x:Key="EmptyDateConverter" />
</Grid.Resources>
<i:Interaction.Behaviors>
<icore:EventTriggerBehavior EventName="Holding">
<converters:OpenMenuFlyoutAction />
</icore:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="AddToCalendarMenuItem" Command="{Binding AddToCalendar}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<Image Grid.Column="0" Source="{Binding ThumbnailUri}"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource ListItemTitle}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Uid="DvdReleaseDate" />
<TextBlock Text="{Binding DvdRelease, Converter={StaticResource EmptyDateConverter}}" />
</StackPanel>
</StackPanel>
</Grid>
2) And here is the list view:
<ListView Grid.Row="1" x:Name="SearchListView"
ItemsSource="{Binding SearchList}"
ItemTemplate="{StaticResource SearchListTemplate}" SelectionChanged="NavigateToMovieDetails" />
My ViewModel is a static kind of singleton in the app.xaml.cs
I've tried to create a new instance of the VM in xaml, but it didn't work - maybe I was doing smth wrong.
I would really appreciate Your help! Thanks in advance.
Best regards,
Roman
If you stick with that <ItemTemplate> you will have to have a Command per every Model in your ViewModel, which is not ideal.
To set a single Command and have a CommandParameter see this SO Tutorial I made it is too much code to type here:
Implement a ViewModel Single Command with CommandParamater
#Chubosaurus Software following your approach I came up with this.
Here is a ListView bound to a list of todo items placed inside a DataTemplate which contains a TextBlock having a MenuFlyout to show edit, delete context menu kind of thing.
The key to bind the commands in the view model to the MenuFlyoutItem is to give the ListView a name and do element binding using the ElementName property in the Command to point to the ListView's name. To access the commands in our view model we've to go through the ListView's DataContext and bind it to a command on it because the DataContext of the MenuFlyoutItem is an item in the ItemsSource
The MainPage.xaml
<Page
x:Class="UWA.MenuFlyout.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWA.MenuFlyout"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UWA.MenuFlyout.ViewModels"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:common="using:UWA.MenuFlyout.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="24,24">
<ListView x:Name="Todos" ItemsSource="{Binding Todos}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Action}">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="edit"
Command="{Binding ElementName=Todos, Path=DataContext.EditTodo}"
CommandParameter="{Binding}"/>
<MenuFlyoutItem Text="delete"
Command="{Binding ElementName=Todos, Path=DataContext.DeleteTodo}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RightTapped">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
The MainPage.xaml.cs is where the DataContext of the MainPage is set.
namespace UWA.MenuFlyout
{
using UWA.MenuFlyout.ViewModels;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.DataContext = new MainViewModel();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// TODO: Prepare page for display here.
// TODO: If your application contains multiple pages, ensure that you are
// handling the hardware Back button by registering for the
// Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
// If you are using the NavigationHelper provided by some templates,
// this event is handled for you.
}
}
}
The MainViewModel.cs containing the Todos which is an ObservableCollection type and the EditTodo and DeleteTodo commands.
namespace UWA.MenuFlyout.ViewModels
{
using System.Collections.ObjectModel;
using System.Windows.Input;
using UWA.MenuFlyout.Core;
using UWA.MenuFlyout.Models;
public class MainViewModel : BaseViewModel
{
private ICommand editTodo;
private ICommand deleteTodo;
public MainViewModel()
{
this.Todos = new ObservableCollection<TodoModel>
{
new TodoModel { Id = 1, Action = "Buy Milk", IsDone = true },
new TodoModel { Id = 2, Action = "Buy Groceries", IsDone = false }
};
}
public ObservableCollection<TodoModel> Todos { get; set; }
public ICommand EditTodo
{
get
{
if (this.editTodo == null)
{
this.editTodo = new RelayCommand(this.OnEditTodo);
}
return this.editTodo;
}
}
public ICommand DeleteTodo
{
get
{
if (this.deleteTodo == null)
{
this.deleteTodo = new RelayCommand(this.OnDeleteTodo);
}
return this.deleteTodo;
}
}
public void OnEditTodo(object parameter)
{
// perform edit here
var todo = parameter as TodoModel;
}
public void OnDeleteTodo(object parameter)
{
// perform delete here
var todo = parameter as TodoModel;
}
}
}
The Model
namespace UWA.MenuFlyout.Models
{
public class TodoModel
{
public int Id { get; set; }
public string Action { get; set; }
public bool IsDone { get; set; }
}
}
The BaseViewModel which implements the INotifyPropertyChanged.
namespace UWA.MenuFlyout.Core
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
A simple ICommand implementation.
namespace UWA.MenuFlyout.Core
{
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action(parameter);
}
}
}
The OpenMenuFlyoutAction which implements DependencyObject and IAction to open the MenuFlyout by using the Execute method on the IAction interface.
namespace UWA.MenuFlyout.Core
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
var frameworkElement = sender as FrameworkElement;
var flyoutBase = FlyoutBase.GetAttachedFlyout(frameworkElement);
flyoutBase.ShowAt(frameworkElement);
return null;
}
}
}

I can't Data Bind to a local variable in WPF/XAML

I want a textbox to display the value of a variable when I click it (an iteration of 1 to 100), I do not know what I am doing Wrong:
When I run the project nothing is displayed in the text box.
What is the best way to display variables in a text box?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace dataBindingTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string myText { get; set; }
public void Button_Click_1(object sender, RoutedEventArgs e)
{
int i = 0;
for (i = 0; i < 100; i++)
{
myText = i.ToString();
}
}
}
}
XAML:
<Window x:Class="dataBindingTest.MainWindow"
Name="windowElement"
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" HorizontalAlignment="Left" Height="106" Margin="71,95,0,0" VerticalAlignment="Top" Width="125" Click="Button_Click_1"/>
<TextBlock x:Name="myTextBox" HorizontalAlignment="Left" Height="106" Margin="270,95,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="187" Text= "{Binding myText, ElementName=windowElement}" />
</Grid>
</Window>
Your current myText property has no way of notifying the WPF binding system when its value has changed, so the TextBlock wont be updated.
If you make it a dependency property instead it automatically implements change notification, and the changes to the property will be reflected in the TextBlock.
So if you replace public string myText { get; set; } with all of this code it should work:
public string myText
{
get { return (string)GetValue(myTextProperty); }
set { SetValue(myTextProperty, value); }
}
// Using a DependencyProperty as the backing store for myText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty myTextProperty =
DependencyProperty.Register("myText", typeof(string), typeof(Window1), new PropertyMetadata(null));
implement INotifyPropertyChanged:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
this.InitializeComponent();
}
private string _txt;
public string txt
{
get
{
return _txt;
}
set
{
if (_txt != value)
{
_txt = value;
OnPropertyChanged("txt");
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
txt = "changed text";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<TextBox Text="{Binding txt}"/>
<Button Click="Button_Click">yes</Button>
and don't forget about adding the DataContext property of your window:
<Window ... DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
Try this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public string myText { get; set; }
public void Button_Click_1(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += delegate
{
int i = 0;
for (i = 0; i < 100; i++)
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() => { myText = i.ToString(); OnPropertyChanged("myText"); }));
Thread.Sleep(100);
}
};
bw.RunWorkerAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
XAML file:
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Height="106" Margin="71,95,0,0" VerticalAlignment="Top" Width="125" Click="Button_Click_1"/>
<TextBlock x:Name="myTextBox"
HorizontalAlignment="Right" Height="106" Margin="0,95,46,0"
TextWrapping="Wrap" VerticalAlignment="Top" Width="187"
Text= "{Binding myText}" />
</Grid>
You should implement INotifyPropertyChanged in your "MainWindow" so your "myTextBlock" can automatically pick up changes from your data and update.
So your "MainWindow" should look like:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private string _myText;
public string myText {
get{return _myText;}
set{_myText = value;
if(PropertyChanged!=null) PropertyChanged(this, new PropertyChangedEventArgs("myText")) ;
}
}
public event PropertyChangedEventHandler PropertyChanged;
etc.....
}
You need to make the property tell the binding that it has updated. The standard way to do this is via:
Implementing INotifyPropertyChanged
Making the myText property a DependencyProperty
Another maybe less used way is to raise the event manually, like this:
public void Button_Click_1(object sender, RoutedEventArgs e)
{
myText = "Clicked";
BindingOperations.GetBindingExpressionBase(myTextBox, TextBlock.TextProperty).UpdateTarget();
}
Note that your TextBlock has the confusing name myTextBox

Categories