I have a Silverlight combo box outside of a grid which works fine.
However, I cannot get it to work properly inside the data grid. I am not certain what I am doing incorrectly. Help with this is greatly appreciated!
This code works fine for the silverlight combo box outside of the grid:
XAML:
<ComboBox Height="23" HorizontalAlignment="Left" ItemsSource="{Binding ElementName=comboBoxItemDomainDataSource, Path=Data}" Margin="112,72,0,0" Name="comboBoxItemComboBox" VerticalAlignment="Top" Width="185" SelectionChanged="comboBoxItemComboBox_SelectionChanged" DisplayMemberPath="ComboDisplayValue">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
<riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my:ComboBoxItem, CreateList=true}" Height="0" LoadedData="comboBoxItemDomainDataSource_LoadedData" Name="comboBoxItemDomainDataSource" QueryName="GetComboboxItems_PatIdEssentrisQuery" Width="0">
<riaControls:DomainDataSource.DomainContext>
<my:ComboBoxItemContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
Combo Box Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace CorporateHR.Web
{
public class ComboBoxItem
{
[Key]
public int ComboID_Int { get; set; }
public string ComboDisplayValue { get; set; }
private static List<ComboBoxItem> GetComboBoxItems(string strStoredProcedure)
{
SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["RefConnectionString"].ConnectionString);
SqlCommand cmd = new SqlCommand(strStoredProcedure, con);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
List<ComboBoxItem> comboList = new List<ComboBoxItem>();
con.Open();
SqlDataReader dr = cmd.ExecuteReader(behavior: CommandBehavior.CloseConnection);
while (dr.Read())
{
ComboBoxItem ComboBoxItem = new ComboBoxItem();
ComboBoxItem.ComboID_Int = Convert.ToInt32(dr[0].ToString());
ComboBoxItem.ComboDisplayValue = dr[1].ToString();
comboList.Add(ComboBoxItem);
}
return comboList;
}
public static List<ComboBoxItem> GetComboboxItems_PatIdEssentris()
{
return GetComboBoxItems("uspLookupPatIdEssentris");
}
//Secondary ComboBox Lookup:
public static List<ComboBoxItem> GetComboboxItems_ORStatus()
{
return GetComboBoxItems("uspLookupORStatus");
}
}
}
Combo Box Domain Service:
namespace CorporateHR.Web
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public class ComboBoxItemService : DomainService
{
public IEnumerable<ComboBoxItem> GetComboboxItems_PatIdEssentris()
{
return ComboBoxItem.GetComboboxItems_PatIdEssentris();
}
public IEnumerable<ComboBoxItem> GetComboboxItems_ORStatus()
{
return ComboBoxItem.GetComboboxItems_ORStatus();
}
}
}
Code Behind Page (for combo box which populates):
private void comboBoxItemDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)
{
if (e.HasError)
{
System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
e.MarkErrorAsHandled();
}
}
private void comboBoxItemComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
I used a templated column like:
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="2" VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{Binding Path=Option0, Mode=OneWay}" Width="Auto" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Height="23" Name="cbx0" SelectedValuePath="Display" DisplayMemberPath="Display"
SelectedValue="{Binding Path=Option0, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource DataContextProxy},Path=DataSource.ocList0}"
MinWidth="65"
Width="Auto">
</ComboBox>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
Where _ocList0 is of type ObservableCollection<cComboBoxOption>
And this is the cComboBoxOption class:
public class cComboBoxOption
{
public int Id { get; set; }
public string Display { get; set; }
public cComboBoxOption(int id, string name)
{
this.Id = id;
this.Display = name;
}
}
This was written in a generic way because I didn't know what the bindings would be or what the combo box would contain until run time.
A simpler way to do this is to use List<string> see the blog post HERE.
Related
Very new to WPF, followed along some youtube tutorials utilizing MVVM and Caliburn Micro, and had the ActiveItem working properly so I am sure my set up is sound. Since completing that tutorial I have tried to customize my app and my intended feature with ActiveItem does not work. App launches with no errors and my login successfully retrieves information from the database and logs me in and displays my name in the bottom left corner.
Here is the intended function:
I have my ShellView as the login page, and after the user signs in it triggers the active item to be the space behind the login rectangle. This code triggers but the login stays visible. It triggers WelcomeViewModel which is just a rectangle with a blue background. The border login doesn't actually disappear, it should just get covered up by the active window (at least for now).
Clicking the space brings up the ContentControl so I am pretty sure it is high-z order, but something is still not right. If I double click the space, it selects the ContentControl and shows its background color appearing on top of the log in which is desired, but not what I see when the application runs.
Xaml for ShellView:
<Window x:Class="ProductivityTool.Views.ShellView"
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:ProductivityTool.Views"
xmlns:model="clr-namespace:ProductivityTool.ViewModels"
mc:Ignorable="d"
d:DataContext="{x:Type model:ShellViewModel}"
Title="Productivity Tool" Height="900" Width="1200" MinWidth="900" MinHeight="700">
<Grid Background="Transparent" RenderTransformOrigin="0.487,0.497">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<!--Row 1-->
<!--
<TextBlock Text="{Binding Path=FullName, Mode=OneWay}" Grid.Row="1" Grid.Column="0"></TextBlock>
-->
<!--Oneway only pulls from the property, but never overwrote the property because it is one way-->
<!--Row 2-->
<!--
<TextBox MinWidth="100" Grid.Row="2" Grid.Column="0" x:Name="FirstName" HorizontalAlignment="Left"></TextBox>
<TextBox MinWidth="100" Grid.Row="2" Grid.Column="0" x:Name="LastName" HorizontalAlignment="Right"></TextBox>
-->
<!--Row 3-->
<!--OnewaytoSource only overwrites the property, opposite of OneWay-->
<!--
<ComboBox Grid.Row="3" Grid.Column="0" x:Name="People"
SelectedItem="{Binding Path=SelectedPerson, Mode=OneWayToSource}"
DisplayMemberPath="FirstName"
HorizontalAlignment="Left" Width="100"/>
<TextBlock Grid.Column="0" Grid.Row="3" x:Name="SelectedPerson_LastName" HorizontalAlignment="Right"/>
-->
<!--Row 4-->
<!--
<Button x:Name="ClearText" Grid.Row="4" Grid.Column="0">Clear Text</Button>
-->
<!--Row 5-->
<StackPanel x:Name="StackPanel" Orientation="Vertical" Grid.Column="0" Grid.Row="1" Background="Blue">
<Button x:Name="Phonebook" HorizontalAlignment="Center" Width="auto" Margin="10" Click="Phonebook_Click">Phonebook</Button>
<Button x:Name="LoadPageTwo" HorizontalAlignment="Center" Width="auto">RCPS</Button>
</StackPanel>
<!--content control is the main window of the ShellView, different pages are just different active items-->
<Border Margin="272,208,-401,255" Padding="5" BorderThickness="1" Background="Gray" CornerRadius="20" Grid.Column="1" Grid.Row="1">
</Border>
<TextBox x:Name="textBoxEmail" Grid.Column="1" HorizontalAlignment="Left" Height="30" Margin="498,246,-347,433" Grid.Row="1" VerticalAlignment="Center" Width="191"/>
<TextBox x:Name="textBoxUID" Grid.Column="1" HorizontalAlignment="Left" Height="30" Margin="498,327,-346,352" Grid.Row="1" VerticalAlignment="Center" Width="191"/>
<TextBlock FontSize="15" Margin="344,245,-143,433" Text="Employee Number:" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" Padding="3" Height="31"/>
<TextBlock FontSize="15" Margin="363,327,-125,352" Text="Email:" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" Padding="3" Height="30" />
<Button x:Name="Loginbtn" Grid.Column="1" Margin="321,391,-347,278" Grid.Row="1" Content="LOGIN" Click="LoginBtn_Click">
</Button>
<Label x:Name="lblSignedInAs" HorizontalContentAlignment="Center" Margin="10,26,0,0" Grid.Row="2" VerticalAlignment="Top" Height="26" Width="181"/>
<Label x:Name="lblLogged" Content="Logged Out" HorizontalContentAlignment="Center" Margin="10,0" Grid.Row="2" VerticalAlignment="Top" Width="181"/>
<ContentControl Grid.Row="1" Grid.Column="1"
x:Name="ActiveItem" Margin="0,0,-647,0"
Background="Blue"/>
</Grid>
</Window>
Code for ShellViewModel:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Caliburn.Micro;
using ProductivityTool.Models;
using ProductivityTool.Views;
namespace ProductivityTool.ViewModels
{
public class ShellViewModel : Conductor<object>
{
//private string _lblLogged;
//private string lblLoggedInAs;
private string _firstName = "Tim"; // Don't change this
private string _lastName;
private BindableCollection<PersonModel> _people = new BindableCollection<PersonModel>();
private PersonModel _selectedPerson;
public ShellViewModel() //Constructor
{
People.Add(new PersonModel { FirstName = "Tim", LastName = "Corey" });
People.Add(new PersonModel { FirstName = "Bill", LastName = "Jones" });
People.Add(new PersonModel { FirstName = "Sam", LastName = "Yet" });
}
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
NotifyOfPropertyChange(() => FullName); //Whenever a value of first name is changed, update fullname
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyOfPropertyChange(() => LastName);
NotifyOfPropertyChange(() => FullName); //Whenever a value of last name is changed, update fullname
}
}
public String FullName
{
get { return $"{ FirstName } { LastName }"; }
}
public BindableCollection<PersonModel> People
{
get { return _people; }
set { _people = value; }
}
public PersonModel SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
NotifyOfPropertyChange(() => SelectedPerson);
}
}
//Return true or true for yes we can clear the text
public bool CanClearText(string firstName, string lastName)
{
//return !String.IsNullOrWhiteSpace(firstName) || !String.IsNullOrWhiteSpace(lastName);
if (String.IsNullOrWhiteSpace(firstName) && String.IsNullOrWhiteSpace(lastName))
{
return false;
}
else
{
return true;
}
}
//Perameters should start with lowercase, properties should start with uppercase
public void ClearText(string firstName, string lastName)
{
FirstName = "";
LastName = "";
}
public void LoadWelcomePage()
{
ActivateItem(new WelcomeViewModel());
}
public void LoadPageTwo()
{
//ActivateItem(new RegistrationViewModel());
}
}
}
Code-behind for ShellView (database info removed for privacy):
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.Shapes;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using ProductivityTool.ViewModels;
namespace ProductivityTool.Views
{
/// <summary>
/// Interaction logic for ShellView.xaml
/// </summary>
public partial class ShellView : Window
{
private ShellViewModel _viewModel; //this is needed to access methods from the view model, format _viewModel.method();
public ShellView()
{
InitializeComponent();
_viewModel = new ShellViewModel();
this.DataContext = _viewModel;
}
private void LoginBtn_Click(object sender, RoutedEventArgs e)
{
string loginCredentials;
if (string.IsNullOrWhiteSpace(textBoxEmail.Text))
{
loginCredentials = textBoxUID.Text;
}
else
{
loginCredentials = textBoxEmail.Text;
}
if (string.IsNullOrEmpty(loginCredentials))
{
MessageBox.Show("No credentials entered.");
}
else
{
string email = loginCredentials;
SqlConnection con = new SqlConnection("Data Source=source;Database=database;Trusted_Connection=Yes;");
con.Open();
SqlCommand cmd = new SqlCommand("Select * from UProfile where Email='" + email + "'", con);
cmd.CommandType = CommandType.Text;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = cmd;
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
if (dataSet.Tables[0].Rows.Count > 0)
{
string username = dataSet.Tables[0].Rows[0]["UserName"].ToString();
}
else
{
MessageBox.Show("Error");
}
lblLogged.Content = "Signed in as:";
lblSignedInAs.Content = dataSet.Tables[0].Rows[0]["UserName"].ToString(); //Display who is signed in currently
con.Close();
_viewModel.LoadWelcomePage();
}
}
private void Phonebook_Click(object sender, RoutedEventArgs e)
{
_viewModel.LoadWelcomePage();
}
}
}
Here is the WelcomeViewModel class that I want to call:
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ProductivityTool.Models;
namespace ProductivityTool.ViewModels
{
public class WelcomeViewModel : Screen
{
}
}
This is identical to the FirstChildViewModel that the tutorial was based on that was working at one point. Here is that:
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ProductivityTool.ViewModels
{
public class FirstChildViewModel : Screen
{
}
}
Apologies for messy code, I want to include everything so you guys have the full picture. Most of it is legacy from the tutorials I did that I keep for reference. If there is a better way to achieve this effect, please let me know.
I stepped through the code and verified that LoadWelcomePage is triggering. I also tried setting this method to be triggered from the button on the menu to the left and it didn't change anything. I messed with the order of ContentControl in the xaml thinking it's a z-index thing, but that didn't work. Actually assigning Z-index values also didn't work. I removed the login border and all of its children from the screen and just tried to have the ContentControl appear upfront but the Z-index looks proper it doesn't work correctly. I also tried different window and grid backgrounds, like transparent, thinking it was hidden behind either the grid or the window but it always remains hidden. I also tried setting the d:DataContext as recommended by another post but it didn't change anything.
While doing the tutorials, I had the same buttons and views being triggered and it was working, but now that my use-case is slightly different things have broken. I'm mostly convinced it is a z-index issue, but it's a simple layout and things seem to be as they should so I am stuck. Thanks in advance for the help.
After more testing, I find out that my _viewModel.LoadWelcomePage is triggering, but not actually doing anything. Assigning the methods that activateitem to different buttons and clicking those makes everything work fine. Because this is more of a c# question, I will close this question and open another one if I can't figure out how to get my method trigger to work.
I am experimenting with WPF and MVVM.
My ViewModel contains a custom collection (IEnumerable) which has to be rendered in UI. I have added some code to add a new entity to the Model which I suppose to be rendered in UI. But that does not happen.
[1] Am I wrong with the pattern? [2] Why isn't the new entity is not reflected in the View, though I raise INPC?
Please find my code below
Classes.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _06_MVVMTest___Add_to_Model_Reflects_in_View
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Salary { get; set; }
public Employee(int id, string name,int age, int salary)
{
this.Id = id;
this.Name = name;
this.Age = age;
this.Salary = salary;
}
}
public class EmployeeCollection : IEnumerable
{
List<Employee> employees = new List<Employee>();
public EmployeeCollection()
{
employees.Add(new Employee(100, "Alice", 23, 300));
employees.Add(new Employee(100, "Bob", 22, 400));
employees.Add(new Employee(100, "Trude", 21, 200));
}
public IEnumerator GetEnumerator()
{
foreach (Employee emp in employees)
{
if (emp==null)
{
break;
}
yield return emp;
}
}
public void AddNewEmployee()//For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
}
}
public class EmployeeViewModel:INotifyPropertyChanged
{
EmployeeCollection employees=new EmployeeCollection();
public EmployeeCollection Employees
{
get { return employees; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOnEmployeeCollectionChanged()
{
if (PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs("Employees"));
}
}
}
MainWindow.xaml.cs
sing 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 _06_MVVMTest___Add_to_Model_Reflects_in_View
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EmployeeViewModel vm =(EmployeeViewModel) this.DataContext;
vm.Employees.AddNewEmployee();
vm.NotifyOnEmployeeCollectionChanged();
}
}
}
MainWindow.xaml
<Window x:Class="_06_MVVMTest___Add_to_Model_Reflects_in_View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:_06_MVVMTest___Add_to_Model_Reflects_in_View"
Title="Employee Window" Height="350" Width="525">
<Window.DataContext>
<local:EmployeeViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView Height="300" Grid.Row="0" Grid.Column="0" BorderBrush="Blue" ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="LightCoral" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Height="300" Grid.Row="0" Grid.Column="1" BorderBrush="Blue" ItemsSource="{Binding Employees}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="LightCoral" Margin="10,10,10,10">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Height="40" Content="add new employee" Click="Button_Click" />
</Grid>
</Window>
There is a problem with the way WPF handles property changes along with your way of notifying the change. WPF checks to see if your value is still the same; the EmployeeCollection is actually the same as the old one (it is still the same instance and it won't look inside the collection).
One solution is to change the implementation of EmployeeCollection to this:
public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
private ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
public EmployeeCollection()
{
employees.Add(new Employee(100, "Alice", 23, 300));
employees.Add(new Employee(100, "Bob", 22, 400));
employees.Add(new Employee(100, "Trude", 21, 200));
}
public IEnumerator GetEnumerator()
{
return employees.GetEnumerator();
}
public void AddNewEmployee() //For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { employees.CollectionChanged += value; }
remove { employees.CollectionChanged -= value; }
}
}
This introduces the INotifyCollectionChanged interface and removes the need to call vm.NotifyOnEmployeeCollectionChanged() in the main window, since the ObservableCollection<T> underlying the EmployeeCollection will tell WPF that it has changed.
If you do not want to learn about these concepts for now, a quick work around would be to :
public void NotifyOnEmployeeCollectionChanged()
{
var current = this.employees;
this.employees = null;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
this.employees = current;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
Your custom collection should implement INotifyCollectionChanged. Every time you add an item, fire the CollectionChanged event.
public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void AddNewEmployee() //For Test
{
employees.Add(new Employee(200, "Dave", 21, 2000));
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
I've extended an the AutoCompleteBox of Silverlight and overridden the OnDropDownClosed event handler. This works as expected except that the component looses the focus to the Browser once the DropDown is closed.
What do I have to change in order to keep it?
Here's my code:
namespace ITPole.Sphere.Application.Core.Controls
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
public class CustomCompleteBox : AutoCompleteBox
{
public static readonly DependencyProperty SelectedAtCloseProperty =
DependencyProperty.Register(
"SelectedAtClose", typeof(object), typeof(CustomCompleteBox), new PropertyMetadata(null));
public object SelectedAtClose
{
get
{
return this.GetValue(SelectedAtCloseProperty);
}
set
{
this.SetValue(SelectedAtCloseProperty, value);
}
}
protected override void OnDropDownClosed(RoutedPropertyChangedEventArgs<bool> e)
{
base.OnDropDownClosed(e);
this.SelectedAtClose = this.SelectedItem;
}
protected override void OnTextChanged(RoutedEventArgs e)
{
base.OnTextChanged(e);
if (string.IsNullOrEmpty(this.Text))
{
this.SetValue(SelectedAtCloseProperty, null);
}
}
}
}
And the usage in xaml:
<Controls1:CustomCompleteBox x:Name="portfolioAutoCompleteBox"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1"
Margin="2"
DataContext="{Binding Portfolio}"
Style="{StaticResource DefaultAutoCompleteBoxStyle}"
ItemTemplate="{StaticResource DescriptionItemTemplate}"
ValueMemberBinding="{Binding Description, Mode=TwoWay}"
SelectedAtClose="{Binding Value, ValidatesOnDataErrors=True, Mode=TwoWay}"
ItemsSource="{Binding Values}"
Text="{Binding Text, Mode=TwoWay}"
Behaviors:AutoCompleteBoxBehaviors.PopulatingCommand="{Binding PopulationCommand}" />
What version of Silverlight are you using? It works for me with V4.
I have tried everything and got nowhere so I'm hoping someone can give me the aha moment.
I simply cannot get the binding to pull the data in the datagrid successfully.
I have a DataTable that contains multiple columns with of MyDataType
public class MyData
{
string nameData {get;set;}
bool showData {get;set;}
}
MyDataType has 2 properties (A string, a boolean)
I have created a test DataTable
DataTable GetDummyData()
{
DataTable dt = new DataTable("Foo");
dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
dt.Rows.Add(new MyData("Row1C1", true));
dt.Rows.Add(new MyData("Row2C1", false));
dt.AcceptChanges();
return dt;
}
I have a WPF DataGrid which I want to show my DataTable.
But all I want to do is to change how each cell is rendered to show [TextBlock][Button] per cell with values bound to the MyData object and this is where I'm having a tonne of trouble.
My XAML looks like this
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate" DataType="MyData">
<StackPanel Orientation="Horizontal" >
<Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
<TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<dg:DataGrid Grid.Row="1" ItemsSource="{Binding}" AutoGenerateColumns="True"
x:Name="dataGrid1" SelectionMode="Single" CanUserAddRows="False"
CanUserSortColumns="true" CanUserDeleteRows="False" AlternatingRowBackground="AliceBlue"
AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn" />
</Grid>
Now all I do once loaded is to attempt to bind the DataTable to the WPF DataGrid
dt = GetDummyData();
dataGrid1.ItemsSource = dt.DefaultView;
The TextBlock and Button show up, but they don't bind, which leaves them blank.
Could anyone let me know if they have any idea how to fix this.
This should be simple, thats what Microsoft leads us to believe.
I have set the Column.CellTemplate during the AutoGenerating event and still get no binding.
Please help!!!
Edit: Updated to reflect the input of Aran Mulholland (see comment)
Apparently the DataGrid is passing the entire DataRowView to each cell. That's why the binding doesn't work. Your DataTemplate expects the DataContext to be of type MyData, but instead it is of type DataRowView. My proposed (somewhat hack-ish) workaround to get the DataContext you want is to create a custom DataGridTemplateColumn that will extract the necessary item from the DataRowView. The code is below:
<Window x:Class="DataGridTemplateColumnSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate" DataType="DataRowView">
<StackPanel Orientation="Horizontal">
<Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
<TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<dg:DataGrid Grid.Row="1" AutoGenerateColumns="True" x:Name="dataGrid1" SelectionMode="Single"
CanUserAddRows="False" CanUserSortColumns="true" CanUserDeleteRows="False"
AlternatingRowBackground="AliceBlue" AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn"
ItemsSource="{Binding}" VirtualizingStackPanel.VirtualizationMode="Standard" />
</Grid>
</Window>
using System.Data;
using System.Windows;
using Microsoft.Windows.Controls;
namespace DataGridTemplateColumnSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = GetDummyData().DefaultView;
}
private static DataTable GetDummyData()
{
var dt = new DataTable("Foo");
dt.Columns.Add(new DataColumn("OneColumn", typeof(MyData)));
dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
dt.Rows.Add(new MyData("Row1C1", true), new MyData("Row1C2", true));
dt.Rows.Add(new MyData("Row2C1", false), new MyData("Row2C2", true));
dt.AcceptChanges();
return dt;
}
private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var column = new DataRowColumn(e.PropertyName);
column.Header = e.Column.Header;
column.CellTemplate = (DataTemplate)Resources["MyDataTemplate"];
e.Column = column;
}
}
public class DataRowColumn : DataGridTemplateColumn
{
public DataRowColumn(string column) { ColumnName = column; }
public string ColumnName { get; private set; }
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var row = (DataRowView) dataItem;
var item = row[ColumnName];
cell.DataContext = item;
var element = base.GenerateElement(cell, item);
return element;
}
}
public class MyData
{
public MyData(string name, bool data) { nameData = name; showData = data; }
public string nameData { get; set; }
public bool showData { get; set; }
}
}
Note: This approach only appears to work with container virtualization off or in Standard mode. If the VirtualizationMode is set to Recycling the template is not applied.
After finding this thread and having trouble with the code shown here, I ran across this thread on MSDN, and it works much better! No virtualization problems at all so far as I've seen.
http://social.msdn.microsoft.com/Forums/en/wpf/thread/8b2e94b7-3c44-4642-8acc-851de5285062
Code:
private void dataGrid1_AutoGeneratingColumn(object sender, Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(MyData))
{
MyDataGridTemplateColumn col = new MyDataGridTemplateColumn();
col.ColumnName = e.PropertyName; // so it knows from which column to get MyData
col.CellTemplate = (DataTemplate)FindResource("MyDataTemplate");
e.Column = col;
e.Column.Header = e.PropertyName;
}
}
public class MyDataGridTemplateColumn : DataGridTemplateColumn
{
public string ColumnName
{
get;
set;
}
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
// The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
ContentPresenter cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
// Reset the Binding to the specific column. The default binding is to the DataRowView.
BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
return cp;
}
}
I have an AutoCompleteBox inside the CellEditingTemplate for a DataGridTemplateColumn and I am trying to auto-focus on it after a certain number of characters have been entered in a previous column.
I have been able to get the focus to shift and the caret to set appropriately using the BeginInvoke method (described here) but only if the control is a TextBox. when using this method with an AutoCompleteBox the caret does not set nor does the control appear to gain focus.
I attempted to set focus on the AutoCompleteBox manually by obtaining a reference to it inside the setCaretInCurrentCell method and invoking focus but that didn't work either.
I really wannt AutoCompleteBox functionality in this column but the datagrid needs to be optimized for data entry which meeans if the users can't tab or automatically be taken to the next field its a show stopper.
Thanks.
I might be going with the oddest suggestion on this one, but instead of trying to get inside the grid / control tree and be explicit with things, use the Current Column setting.
Ripping off the data template sample in the help files the XAML and basiccode are as follows:
It's not perfect by a long margin in that:
it is just hacking together moving from column 0 to 1 based on the pressing of a space, your requirement on the condition to move focus is more complex.
I've not set up two way binding so the edit persists etc. just looked at how to move column.
But when it runs, it does respond to a space and move the cursor into the autocomplete box, highlights the text and if I start typing the autocomplete drop down activates. So in principle, setting the current column seems to provide the behaviour you are after.
A.
Code:
<UserControl x:Class="SilverlightApplication2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Width="400" Height="300">
<ScrollViewer VerticalScrollBarVisibility="Auto" BorderThickness="0">
<StackPanel Margin="10,10,10,10">
<TextBlock Text="DataGrid with template column and custom alternating row backgrounds:"/>
<data:DataGrid x:Name="dataGrid5"
Height="125" Margin="0,5,0,10"
AutoGenerateColumns="False"
RowBackground="Azure"
AlternatingRowBackground="LightSteelBlue">
<data:DataGrid.Columns>
<!-- Address Column -->
<data:DataGridTemplateColumn Header="Address" Width="300">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Padding="5,0,5,0" Text="{Binding Address}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Padding="5,0,5,0" Text="{Binding Address}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
<!-- Name Column -->
<data:DataGridTemplateColumn Header="Name">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Padding="5,0,5,0"
Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<input:AutoCompleteBox Padding="5,0,5,0"
Text="{Binding FirstName}">
<input:AutoCompleteBox.ItemsSource>
<toolkit:ObjectCollection>
<system:String>January</system:String>
<system:String>February</system:String>
<system:String>March</system:String>
<system:String>April</system:String>
<system:String>May</system:String>
<system:String>June</system:String>
<system:String>July</system:String>
<system:String>August</system:String>
<system:String>September</system:String>
<system:String>October</system:String>
<system:String>November</system:String>
<system:String>December</system:String>
</toolkit:ObjectCollection>
</input:AutoCompleteBox.ItemsSource>
</input:AutoCompleteBox>
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
<Button Content="test"></Button>
</StackPanel>
</ScrollViewer>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication2
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
// Set the ItemsSource to autogenerate the columns.
dataGrid5.ItemsSource = Customer.GetSampleCustomerList();
dataGrid5.KeyDown += new KeyEventHandler(dataGrid5_KeyDown);
}
void dataGrid5_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
// move to next cell and start editing
DataGrid grd = (DataGrid)sender;
if (grd.CurrentColumn.DisplayIndex == 0)
{
// move to column 1 and start the edit
grd.CurrentColumn = grd.Columns[1];
}
}
}
}
public class Customer
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String Address { get; set; }
public Boolean IsNew { get; set; }
// A null value for IsSubscribed can indicate
// "no preference" or "no response".
public Boolean? IsSubscribed { get; set; }
public Customer(String firstName, String lastName,
String address, Boolean isNew, Boolean? isSubscribed)
{
this.FirstName = firstName;
this.LastName = lastName;
this.Address = address;
this.IsNew = isNew;
this.IsSubscribed = isSubscribed;
}
public static List<Customer> GetSampleCustomerList()
{
return new List<Customer>(new Customer[4] {
new Customer("A.", "Zero",
"12 North",
false, true),
new Customer("B.", "One",
"34 West",
false, false),
new Customer("C.", "Two",
"56 East",
true, null),
new Customer("D.", "Three",
"78 South",
true, true)
});
}
}
}