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.
Related
I have a textbox and a datagrid like so:
<Page
TextElement.FontSize="14" FontFamily="Segoe UI"
Title="Delivery View">
<Page.Resources>
<xcdg:DataGridCollectionViewSource x:Key="firstNameDataSource"
Source="{Binding Path=Accessor.Views[FirstNameView].SourceCollection}"
AutoFilterMode="And"
DistinctValuesConstraint="Filtered">
<xcdg:DataGridCollectionViewSource.ItemProperties>
<xcdg:DataGridItemProperty Name="FirstName" CalculateDistinctValues="False"/>
</xcdg:DataGridCollectionViewSource.ItemProperties>
</xcdg:DataGridCollectionViewSource>
</Page.Resources>
<ScrollViewer Name="pendingScroll" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<DockPanel Name="pnlMainPanel" LastChildFill="True" Style="{StaticResource panelBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="18" Text="Pending Guests" Margin="0,1,3,1" Foreground="SteelBlue" HorizontalAlignment="Left"/>
<TextBox Name="txtFirstNameFilter" Grid.Row="1" >
</TextBox>
<xcdg:DataGridControl x:Name="gridPendingGuests" Margin="5,0,5,1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinHeight="100"
MinWidth="200"
CellEditorDisplayConditions="None"
EditTriggers="None"
ItemScrollingBehavior="Immediate"
AutoCreateColumns="False"
SelectionMode="Single"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource firstNameDataSource}}">
<xcdg:DataGridControl.View>
<xcdg:TableView ShowRowSelectorPane="False"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Columns>
<xcdg:Column x:Name="FirstName" FieldName="FirstName" Title="First Name" Width="150" />
</xcdg:DataGridControl.Columns>
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange/>
</i:Interaction.Behaviors>
</xcdg:DataGridControl>
</Grid>
</DockPanel>
</ScrollViewer>
</Page>
In the datagrid, you have a collection of first names. This works perfectly. The display is good. As you can see, I added an Interactions.Behavior class which currently handles a filter with a hard coded value when the user clicks on the datagrid with their mouse. The filtering works fine. If there is a first name of "John", that record is removed from view, leaving all other records in place.
Here is that code:
using System.Windows.Interactivity;
using System.Windows;
using Xceed.Wpf.DataGrid;
using System;
namespace Some.Namespace.Behaviors
{
public class UpdateDataGridOnTextboxChange : Behavior<DataGridControl>
{
protected override void OnAttached()
{
AssociatedObject.MouseUp += AssociatedObjectOnMouseUp;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseUp -= AssociatedObjectOnMouseUp;
base.OnDetaching();
}
private void AssociatedObjectOnMouseUp(object sender, RoutedEventArgs routedEventArgs)
{
var items = AssociatedObject.Items;
items.Filter = CollectionFilter;
}
private bool CollectionFilter(object item)
{
System.Data.DataRow dr = item as System.Data.DataRow;
//set the ItemArray as Guest
Guest guest = SetGuest(dr);
if (guest.FirstName.Equals("John"))
{
return false;
}
return true;
}
private Guest SetGuest(System.Data.DataRow dr)
{
Guest guest = new Guest();
guest.FirstName = dr.ItemArray[0].ToString();
return guest;
}
public class Guest
{
public string FirstName { get; set; }
}
}
}
This works as expected. Again, when the user clicks on the datagrid, the filter filters out the users with the First Name of "John".
What I WANT to have happen is for the user to be able to type a first name in the txtFirstNameFilter Textbox and the datagrid to then filter the records that contain the text in the first name, keeping them visible and the others without that first name to not be visible.
The way I can do it is with an attached property of the Textbox TextChanged property? That's a question, because I don't know how to do an attached property and then how to make sure that when that attached property actually changes, call the AssociatedObjectOnMouseUp method to run the filtering.
System.Windows.Interactivity.Behavior<T> inherits from DependencyObject. So give it a dependency property and bind that.
public class UpdateDataGridOnTextboxChange : Behavior<DataGrid>
{
#region FilterValue Property
public String FilterValue
{
get { return (String)GetValue(FilterValueProperty); }
set { SetValue(FilterValueProperty, value); }
}
public static readonly DependencyProperty FilterValueProperty =
DependencyProperty.Register(nameof(FilterValue), typeof(String), typeof(UpdateDataGridOnTextboxChange),
new FrameworkPropertyMetadata(null, FilterValue_PropertyChanged));
protected static void FilterValue_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as UpdateDataGridOnTextboxChange).OnFilterValueChanged(e.OldValue);
}
private void OnFilterValueChanged(object oldValue)
{
// Do whatever you do to update the filter
// I did a trace just for testing.
System.Diagnostics.Trace.WriteLine($"Filter value changed from '{oldValue}' to '{FilterValue}'");
}
#endregion FilterValue Property
/*****************************************
All your code here
*****************************************/
}
XAML:
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange
FilterValue="{Binding Text, ElementName=txtFirstNameFilter}"
/>
</i:Interaction.Behaviors>
You should rename it, though. It's got nothing to do with text boxes. You could bind FilterValue to a viewmodel property, or the selected value in a ComboBox, or whatever.
Update
OP's having trouble with the binding only updating FilterValue when the text box loses focus. This isn't what I'm seeing, but I don't know what's different between the two.
There isn't any UpdateTargetTrigger property of Binding, but you can swap the source and the target when both are dependency properties of dependency objects. This works for me:
<TextBox
x:Name="txtFirstNameFilter"
Text="{Binding FilterValue, ElementName=DataGridFilterThing, UpdateSourceTrigger=PropertyChanged}"
/>
<!-- snip snip snip -->
<i:Interaction.Behaviors>
<local:UpdateDataGridOnTextboxChange
x:Name="DataGridFilterThing"
/>
</i:Interaction.Behaviors>
I'm building a series of admin forms as part of my application.
Many of the forms have this same scenario, and I'm wondering what the best way to manage this is...
Here's a screenshot of an example from the old winforms app. Not all of the forms are this simple, but I figure it's a good place to start.
The first field is always a combo box. After a user selects an item, the other fields populate with the associated data.
Currently, my ViewModel contains an ObservableCollection<circuit> (for this example). I'm not sure how to set up my bindings, given the master/detail scenario where your master (primary combobox) is also part of your detail (CircuitName field). I was thinking of pulling the combobox out and, after selecting an item, displaying a datagrid. Is there a better way? If I do go this route, I have read a few articles that indicate binding a combobox INSIDE the datagrid, while property binding the selectedValue prop is a total PITA. Obviously having a combobox would be required for a user to create/edit an existing CircuitName...
Your thoughts?
I'm not sure I fully understand, but if I was using MVVM Light, like I think you are:) And I wanted to replicated the screen above I would do something like this. But I'm probably not 100% understanding what you mean.
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.ColumnSpan="2">
<TextBlock Text="Circuit Name"/>
<ComboBox SelectedValue="{Binding SelectedCircuit,Mode=TwoWay}" ItemsSource="{Binding Circuits}" DisplayMemberPath="Name"/>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<TextBlock Text="Circuit Code"/>
<TextBlock Text="{Binding SelectedCircuit.Code}"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBlock Text="Voltage"/>
<TextBlock Text="{Binding SelectedCircuit.Voltage}"/>
</StackPanel>
<Button Grid.Row="2" Content="Okay"></Button>
<Button Grid.Row="2" Grid.Column="1" Content="Cancel"></Button>
</Grid>
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
using MvvmLight1.Model;
namespace MvvmLight1.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Circuits = new ObservableCollection<Circuit>
{
new Circuit {Code = "123123", Name = "Test1", Voltage = 2.2, Id = 1},
new Circuit {Code = "14224", Name = "Test2", Voltage = 3.2, Id = 2},
new Circuit {Code = "54234", Name = "Test3", Voltage = 4.2, Id = 3},
};
}
public ObservableCollection<Circuit> Circuits { get; set; }
private Circuit _selectedCircuit;
public Circuit SelectedCircuit
{
get { return _selectedCircuit; }
set { _selectedCircuit = value;
RaisePropertyChanged("SelectedCircuit");
}
}
}
}
namespace MvvmLight1.Model
{
public class Circuit
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public double Voltage { get; set; }
}
}
Answer was really simple actually.
I have my ObservableCollection<Circuit> Circuits that's loaded in the VM Constructor. I created a ObservableCollection<Circuit> SelectedCircuit. My xaml then looks like this:
<ComboBox ItemsSource="{Binding Configs}"
SelectedValue="{Binding SelectedConfig, Mode=TwoWay}"
DisplayMemberPath="SwitchCode"
/>
<toolkit:DataForm CurrentItem="{Binding SelectedConfig}" AutoEdit="False" />
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.
While designing a new WPF application I noticed exceptions not being thrown during data binding for controls with DataTemplates. To test I wrote the following simple user control with as little logic as possible. I'm using .NET 3.5 SP1, VS2008 SP1 running on WIN7.
When the DataContext is set, TestMethod is called and an exception is thrown in the codebehind but the application doesn't break.
Would someone mind explaining what is happening when the DataTemplate is being created? Specifically I'm interested in where the exception goes.
Below is the XAML code:
<UserControl x:Class="WPF2008.DataTemplateQuestion"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<DataTemplate x:Key="TESTKey">
<Border BorderBrush="Black" BorderThickness="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Path=Txt}" />
<TextBox Grid.Row="1" Text="{Binding Path=Lbl}" />
<TextBox Grid.Row="2" Text="Am I disabled?" />
<TextBox Grid.Row="3" Text="I am disabled." IsEnabled="False" />
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Label x:Name="lblTest" Content="{Binding Path=TestMethod}" ContentTemplate="{StaticResource TESTKey}" />
</Grid>
</UserControl>
Below is the codebehind:
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 WPF2008
{
public partial class DataTemplateQuestion : UserControl
{
public DataTemplateQuestion()
{
try
{
InitializeComponent();
lblTest.DataContext = new TestClass();
}
catch (Exception e)
{
// ADDING A BREAKPOINT HERE DOESN'T CATCH THE EXCEPTION
throw e;
}
}
}
public class TestClass
{
public TestValue TestMethod
{
get
{
// BREAKPOINT WILL BE HIT WHEN DEBUGGING BUT THE EXCEPTION DISAPPEARS
throw new Exception("WATCH ME DISAPPEAR");
return new TestValue { Lbl = "Label", Txt = "Text" };
}
}
public class TestValue
{
public string Lbl { get; set; }
public string Txt { get; set; }
}
}
}
Exceptions thrown by data bindings get translated into trace, which are delivered to the DataBindingSource TraceSource, which has a source name of "System.Windows.Data".
You can convert these into exceptions by creating a subclass of TraceListner that throws an exception on a trace, and add it to the Listeners collection of the TraceSourceSource. This can be done either in code or in your App.config file.
Here is how you would do it in code:
System.Diagnostics.PresentationTraceSources.DataBindingSource.Listeners.Add(
new MyTraceListener());
See TraceSource and TraceListener documentation and samples for more details.
Exceptions get eaten by data bindings, and result in the data binding being disabled (I'm not 100% sure this is the case here, I'm too lazy to put it into VS).
Another possibility is that you may be hitting http://support.microsoft.com/kb/976038 if you are running a 64-bit OS.
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)
});
}
}
}