I'm having a dataform which is binded to a property in my view-model in a Silverlight application, I've created my entity classes with WCF RIA Services and every property has the attribute of DisplayName which is shown in the dataform datafield label. what I need to do is to add a ":" at the end of every label in the custom datafields that I create.
The reason I need this to happen is because I have a grid in my page which is binded to the list of current objects (e.g. Employees) and I don't want ":" at the end of the grid headers, but I also need ":" when I'm trying to edit or add a new employee.
This is what I've done so far, but it's not working.
public class CustomDataField : DataField
{
public CustomDataField()
{
}
public new object Label
{
get { return base.Label; }
set
{
base.Label = value;
if( value is string )
{
base.Label = (string)value + ":";
}
}
}
}
(1)
When you don't let the DataForm autogenerate the fields, you have more control over the fields and can set the labels manually:
<tkt:DataForm AutoGenerateFields="False" AutoEdit="True">
<tkt:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<tkt:DataField Label="SomeLabel:">
<TextBox Text="{Binding SomeProperty, Mode=TwoWay}" />
</tkt:DataField>
[...]
</StackPanel>
</DataTemplate>
</tkt:DataForm.EditTemplate>
</tkt:DataForm>
(2)
If you need the auto-generating functionality, but you also need more control over how fields are displayed, you could wrap the DataForm into your own custom control. You'll have to implement the auto-generation yourself to build your own EditTemplate, which you'd assign to the DataForm. This is the road that I took.
(3)
Another quick and dirty way would be to iterate through the visual tree after the DataForm has rendered to change the labels. That goes pretty straightforward with a little help from the toolkit:
// needs System.Windows.Controls.Toolkit.dll
using System.Linq;
using System.Windows.Controls.Primitives;
foreach (var field in myDataForm.GetVisualDescendents().OfType<DataField>())
{
field.Label = field.Label + ":";
}
(4)
Finally, I just saw that there is an AutoGeneratingField event on the DataForm that could work (untested):
myDataForm.AutoGeneratingField += (sender, e) => e.Field.Label = e.Field.Label + ":";
Related
I'm writing WPF application with MVVM structure using MVVM Light.
I have class Foo in the Model:
class Foo: ObservableObject
{
private string _propA = String.Empty;
public string PropA
{
get => _propA ;
set
{
if (_propA == value)
{
return;
}
_propA = value;
RaisePropertyChanged("PropA");
}
}
// same for property PropB, PropC, PropD, etc.
}
And I have some collection of Foo objects in the Model:
class FooCollection: ObservableObject
{
private ObservableCollection<Foo> _items = null;
public IEnumerable<Foo> Items
{
get { ... }
set { ... }
}
public string Name { get; set; }
// ...
// and other methods, properties and fields
}
Now I have a ViewModel where this list is populated via some injected provider:
class MainWindowModel: ViewModelBase
{
private FooCollection _fooList;
public FooList
{
get => _fooList;
set
{
_fooList = value;
RaisePropertyChangedEvent(FooList);
}
}
public MainWindowModel(IFooListProvider provider)
{
FooList = provider.GetFooList();
}
}
And the View, with MainWindowModel as data context:
<TextBlock Text={Binding FooList.Name} />
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Everything works fine, I can delete and add new items, edit them and etc. All changes in View reflects automatically in ViewModel and Model via bindings and observable objects, and vice versa.
But now I need to add ToggleButton to data template of ItemsControl, which controls visibility of particular item in other part of window. I need IsChecked value in ViewModel, because control in other part of window is Windows Forms control and I can't bind IsChecked directly without ViewModel.
But I don't want to add new property (Visibility, for example) in model classes (Foo, FooCollection), because it is just an interface thing and it doesn't need to be saved or passed somewhere outside ViewModel.
So my question: what is the best way to add new property to Model collection in ViewModel?
I could create new collection of wrappers in ViewModel (some sort of class Wrapper { Foo item, bool Visibility }) and bind it to ItemsControl. But in this case I have to control adding, removing and editing manually and transfer all changes from List<Wrapper> to FooList.Items, so I don't like this solution. Is there any more simple way to achieve this?
Edition to clarify the question. Now I have:
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<ToggleButton IsChecked={Binding ????????????} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have no field in class to bind IsChecked and I don't want to add it to class, because it's only interface thing and not data model field. How can I, for example, create another collection of bools and bind it to this ItemControl alongside with FooList.Items?
The best place to add the property is of course in the Foo class.
Creating another collection of some other type, add an object per Foo object in the current collection to this one, and then bind to some property of this new object seems like a really bad solution compared to simply adding a property to your current class.
Foo is not an "interface thing", or at least it shouldn't be. It is view model that is supposed to contain properties that the view binds to. There is nothing wrong with adding an IsChecked property to it. This certainly sounds like the best solution in your case.
I'm not sure if I understand why you would need to add a property in the model.
Can't you just use the command property or add an EventTrigger to your toggle button?
(See Sega and Arseny answer for both examples Executing a command on Checkbox.Checked or Unchecked )
This way, when you check the toggleButton, there is a method in your viewModel which enable or disable the visibility property of your Winform control.
To change the visibility of your control from a command in your viewModel, you could use the messenger functionnality of MVVM LIGHT
MVVM Light Messenger - Sending and Registering Objects
The ViewModel sends a message to you're Windows Forms and this one handles the visibility of your control.
I have an MVVM C#/WPF app. I'd like to bind a XamDataGrid to one of my view models, so that users can define how they want their results structure - what column is mapped to what variable.
My core holds such dictionary - Dictionary<string, string>, and I'd like to wrap it in an ObservableCollection and bind to this, rather than copy the data to a DataTable. Problem is, I can't add rows this way (the view seems locked for additions) and changes are not saved. My code:
<igDP:XamDataGrid
Grid.Row="1" Grid.Column="0"
Name="resultStructure"
DataSource="{Binding VariablesDictionary, Mode=TwoWay}"
GroupByAreaLocation="None">
<igDP:XamDataGrid.FieldLayoutSettings>
<igDP:FieldLayoutSettings
AllowFieldMoving="WithinLogicalRow"
AllowAddNew="True"
AllowDelete="True"
AddNewRecordLocation="OnBottomFixed" />
</igDP:XamDataGrid.FieldLayoutSettings>
</igDP:XamDataGrid>
the view model (relevant part):
private ObservableCollection<DictionaryEntry> variablesDictionary;
public ObservableCollection<DictionaryEntry> VariablesDictionary
{
get { return variablesDictionary; }
set
{
variablesDictionary = value;
OnPropertyChanged(()=>VariablesDictionary);
}
}
...
List<DictionaryEntry> vars = resultStructureModel.Variables.Select(x => new DictionaryEntry {Key = x.Key, Value = x.Value}).ToList();
VariablesDictionary = new ObservableCollection<DictionaryEntry>(vars);
For adding rows, the XamDataGrid uses either IEditableCollectionView.CanAddnew or IBindingList.AllowNew.
If you use a ListCollectionView for the DataSource of the grid then you can add new rows.
To use the ListCollectionView with an ObservableCollection, pass the ObservableCollection into the ListCollectionView and then use the CollectionView to bind to the grid.
What worked eventually was using a BindingList. ObservableCollection won't let you add rows, but BindingList really provides everything I need.
i am currently creating a Gridview using telerik control which displays data from the sql database which i displays through domain datasource used in wcf ria.(ADO.net entity model etc)
i want to add an autocomplete box above my radgrid where i type an name and other matchable entries are also listed.
when i click on the entry then radgrid may display whole row containing that name.
i am using silverlight 4,wcf ria,telerik controls.
please provide a sample coding idea in xaml and xaml.cs.
i tries to access telerik demos but they are not running on my system.
As an example... say you have a list of Customers, of which you want to display their names in your AutoComplete box. Further, your Grid should display all customers, and when a Name is selected in the AutoComplete box, the Selected item of the grid displays.
What you need to do is bind the SelectedItem property of the RadGridView & AutoCompleteBox. What I would do is bind the AutoCompleteBox to a property named SelectedName, like so:
<input:AutoCompleteBox ItemsSource="{Binding Names}" SelectedItem="{Binding SelectedName, Mode=TwoWay}" />
Emphasis on the 'Mode=TwoWay' - this is what will alert your code behind that the UI has changed.
In your code behind, you would create properties like this:
private string selectedName;
public string SelectedName
{
get { return selectedName; }
set
{
if (value != null)
{
var query = (from c in CustomersList
where (c.Name == value)
select c).FirstOrDefault();
SelectedCustomer = (Customer)query;
selectedName = value;
}
}
}
Notice how, when you're setting the SelectedName, you're using LINQ to determine which of the customers were selected. One pitfall here would be if you have multiple names in a list... this code only selects the first. If this is an issue, you probably should rethink your architecture..
Then for your grid, you would bind the SelectedItem like so:
<telerik:RadGridView
....
SelectedItem={Binding SelectedCustomer, Mode=TwoWay"}
....
</telerik:RadGridView>
In your code behind, you'd create this property:
private Customers selectedCustomer;
public Customers SelectedCustomer
{
get { return selectedCustomer; }
set {
selectedCustomer = value;
MyGridView.SelectedItem = selectedCustomer;
}
}
Something like that should get you started.
SS
I am using the DataForm for an entity with about 40 attributes. I'm happy with how the form displays all but 3 of the attributes. These 3 attributes happen to be lists of items.
I don't want to have to code out an entire edit template, seems very counter productive.
<dataFormToolkit:DataForm AutoGenerateFields="True" CurrentItem="{Binding XXX, Mode=TwoWay, Source={StaticResource XXXViewModel}}" >
<dataFormToolkit:DataField Label="Client" >
<ListBox ItemsSource="{Binding Client}"></ListBox>
</dataFormToolkit:DataField>
</dataFormToolkit:DataForm>
The the WCF RIA Services includes a Silverlight Business Application project template that demonstrates creating a CustomDataForm where they override OnAutoGeneratingField and modify the field for just the attributes you want. I've copied the code here for you to illustrate the idea but I'd suggest you check out the real thing to see how they are using the ReplaceTextBox extension method to deal with the Data Binding as well. Download link.
public class CustomDataForm : DataForm
{
protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)
{
// Get metadata about the property being defined
PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);
// Do the password field replacement if that is the case
if (e.Field.Content is TextBox && this.IsPasswordProperty(propertyInfo))
{
e.Field.ReplaceTextBox(new PasswordBox(), PasswordBox.PasswordProperty);
}
// Keep this newly generated field accessible through the Fields property
this.fields[e.PropertyName] = e.Field;
// Call base implementation (which will call other event listeners)
base.OnAutoGeneratingField(e);
}
}
It will work : try that
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class IsPassword : System.Attribute { }
public class CustomDataForm : DataForm
{
protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)
{
// Get metadata about the property being defined
PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);
// Do the password field replacement if that is the case
var attributes = propertyInfo.GetCustomAttributes(typeof(IsPassword), false).ToList();
if (attributes.Any(obj=>obj is IsPassword))
{
PasswordBox box= new PasswordBox();
Binding binding = new Binding(e.PropertyName);
binding.Mode = BindingMode.TwoWay;
box.SetBinding(PasswordBox.PasswordProperty, binding);
e.Field.Content=box;
}
base.OnAutoGeneratingField(e);
}
}
then just add [IsPassword] to your property
I'm pretty sure it's not possible. If I were you I would swallow my grief and create that edit template.
The only alternative I can see is to work with the data in your viewmodel and create a separate class that holds the 37 properties that need no changing. Then you make a separate entity for the 3 that need special attention. This way you could have two data forms, one autogenerated and one custom. Hopefully you can then work with styling them so they look like one form. A lot of work, I know, but it might be even more work to create the full edit template.
I would like to have a ComboBox control on a form which will be used to search a list of investments as the user types. I can do this easily if I cache the entire collection of investments from the database on startup (currently 3,000 or so items), but I would prefer to not do that if it isn't necessary.
The behavior that I am trying to implement is:
The user types text into the editable ComboBox.
As the user enters each character, the database search function is triggered, narrowing down the search results with each successive keystroke.
As the search results are updated, the dropdown panel opens and displays the relevant matches
I have tried binding the Text property of the ComboBox to the InvestmentName (string) property on my ViewModel, and the ItemsSource property of the ComboBox to the InvestmentList (generic List) property on my ViewModel. When I do this, the Text property auto-completes from the ItemsSource, however the dropdown appears empty.
I have been able to achieve these results using a TextBox stacked on top of a ListBox, but it isn't very elegant and it takes up more screen real estate. I've also been able to get it to work with a TextBox stacked on top of a ComboBox, although the ComboBox steals the focus when the IsDropDownOpen property is set to "true" when there are valid search items. It also isn't very visually pleasing to use two controls for this.
I feel like I'm really close to getting it to work the way I want it to, but there is something which eludes me.
The XAML for this control is:
<ComboBox Height="23" Width="260" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left"
ItemsSource="{Binding InvestmentList}" DisplayMemberPath="FullName"
IsDropDownOpen="{Binding DoShowInvestmentList}"
ItemsPanel="{DynamicResource ItemsTemplate}" IsEditable="True"
Text="{Binding Path=InvestmentName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
The relevant ViewModel properties are:
private bool _doShowInvestmentList;
public bool DoShowInvestmentList
{
get { return _doShowInvestmentList; }
set { if (_doShowInvestmentList != value) { _doShowInvestmentList = value; RaisePropertyChanged("DoShowInvestmentList"); } }
}
private List<PFInvestment> _investmentList;
public List<PFInvestment> InvestmentList
{
get { return _investmentList; }
set { if (_investmentList != value) { _investmentList = value; RaisePropertyChanged("InvestmentList"); } }
}
private string _investmentName;
public string InvestmentName
{
get { return _investmentName; }
set
{
if (_investmentName != value)
{
_investmentName = value;
this.InvestmentList = DataAccess.SearchInvestmentsByName(value).ToList();
if (this.InvestmentList != null && this.InvestmentList.Count > 0)
this.DoShowInvestmentList = true;
else
this.DoShowInvestmentList = false;
RaisePropertyChanged("InvestmentName");
}
}
}
I've done a fair bit of research on this, but I haven't quite found the answer yet.
Check out this great article on CodeProject by... me :)
A Reusable WPF Autocomplete TextBox
Look towards the end for the Google suggest example, it is similar to what you need, where every keypress triggers another query to the server.