— placeholder, have to make this
I think it’s one of the most common scenarios around, you would like to bind a button set for use of typical operations.
See those 3 little buttons next to the title? It’s a very common case, having 3 generic buttons for CRUD operations over data right next to datagrid for ease of use. It can be whatever and you could effectively put there a few more, but this will suffice for showing off a nice way how caliburn.micro can make your life easier.
So for instance you have this control with these generic buttons, and you called it ListButtons:
<UserControl x:Class="AltinetSilver.AltiControls.ListButtons"
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:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls" mc:Ignorable="d"
VerticalAlignment="Bottom" Margin="5,0,5,0">
<UserControl.Resources>
<SolidColorBrush x:Key="BorderBrush" Color="#FF767676" />
</UserControl.Resources>
<StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<Button x:Name="Add" BorderBrush="Transparent" BorderThickness="0,0,0,0" Visibility="{Binding AddNewButtonVisibility}" Height="18" Margin="2,4,2,4" Width="18"/>
<Button x:Name="Edit" BorderBrush="Transparent" BorderThickness="0,0,0,0" Visibility="{Binding EditButtonVisibility}" Height="18" Margin="2,4,2,4" Width="18"/>
<Button x:Name="Delete" BorderBrush="Transparent" BorderThickness="0,0,0,0" Visibility="{Binding DeleteButtonVisibility}" Height="18" Margin="2,4,4,4" Width="19" Padding="0"/>
</StackPanel>
</UserControl>
U use it in your PartnersView.xaml in the following way
.
.
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource HeaderStyle}" VerticalAlignment="Top" Text="{Binding Partners,Source={StaticResource Strings}}"/>
<AltiControls:ListButtons x:Name="PartnerButtons"/>
</StackPanel>
.
.
It’s the code shown from the picture above, buttons next to the header. Notice the name of the control, it’s important for the convention
Your PartnersViewModel.cs looks something like this, with commands for using those buttons:
.
.
public void AddPartner()
{
}
public bool CanAddPartner
{ get { return true; }}
public void DeletePartner()
{
}
public bool CanDeletePartner
{
get { return SelectedPartner != null; }
}
public void EditPartner()
{
}
public bool CanEditPartner
{
get { return SelectedPartner != null; }
}
.
.
So you want to hook up these methods to those generic buttons without any extra work.
First let’s define how a convention would look like
ConventionManager.AddElementConvention<ListButtons>(UserControl.ContentProperty, "DataContext", null)
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
var buttons = element as ListButtons;
if (buttons == null) return;
if (!buttons.Name.EndsWith("Buttons")) return;
var entName = buttons.Name.Remove(buttons.Name.IndexOf("Buttons"));
SetListButton(viewModelType, buttons.Add, "Add" + entName);
SetListButton(viewModelType, buttons.Edit, "Edit" + entName);
SetListButton(viewModelType, buttons.Delete, "Delete" + entName);
};
What we are doing here is searching for naming convention for ListButtons class. It has to start with entity name and end with “Buttons” text. After we find it, we go through buttons and Set them up.
SetListButton function looks like following:
private static void SetListButton(Type modelType,AltiButton button,string methodName)
{
if (modelType.GetMethod(methodName) != null)
Message.SetAttach(button, methodName);
else
button.Visibility = Visibility.Collapsed;
}
So there you have it. This method basically hooks up a button with an appropriate methodname, or it hides the button.
One last thing left to do. We need to define a property in the main viewmodel so we expose the object which holds our button methods. For the previous example, you just add the following to PartnersViewModel.cs
public object PartnerButtons
{
get { return this; }
}
Caliburn will hook the property to “PartnerButtons” user control, and the convention will do the rest. Hope this helps!
Ok, this one was a real pain in the ass. I would get this strange [Format_BadDateTime] error when trying to type in a Date in any of the DatePickers i would try to use. And only on Windows XP.
If i would use it outside the DataForm my binding would just get broken and that would basically be it!
On a different project, with similair setup i would get a [Arg_TargetInvocationException].
The problem occurs only if you’re using non-English localization settings on client computer (which means date formats other than MM/dd/yyyy) on Windows xp.
Well, i figured it out and it’s the Range attribute!
Range attribute is constituted pretyy awkard, ie for custom values it uses property type and string representations of minimum and maximum values. I think you can already see where’s the problem coming from:). Don’t know why it is different on WinXP or why it can’t figure it out there but basically what you need to do is keep it in the invariantCulture to avoid any problems:
((DateTime)someDate).ToString(CultureInfo.InvariantCulture)
where the end product would look something like this:
[Range(typeof(DateTime), @"01/01/1753 00:00:00", @"01/01/9999 00:00:00")]
And that’s it! Lost a good day on that crap, because my silverlight codegenerator used the local culture…
Oh, and i think that this problem is actually connected to my dev machine having different culture setup than english
AutoCompleteBox connected to web service is often needed solution, and there are many solutions out there that are bare bone ones, but using such powerful UI framework as Caliburn.Micro is can reap some interesting benefits. So here’s my implementation.
I decided to subclass AutoCompleteBox because i subclass all of the controls i use. I called it AltiAutoCompleteBox.
A new event was added called PopulatingAsync and a new EventArg called PopulatingAsyncEventArgs:
AltiAutoCompleteBox basically just adds another event PopulatingAsync which is called every time Populating event is called:
public partial class AltiAutoCompleteBox:AutoCompleteBox
{
public event EventHandler<PopulatingAsyncEventArgs> PopulatingAsync;
public AltiAutoCompleteBox()
{
MinimumPopulateDelay = 500;
MinimumPrefixLength = 2;
FilterMode = AutoCompleteFilterMode.None;
// handle the populating event of the associated auto complete box
Populating += AssociatedObject_Populating;
}
private void AssociatedObject_Populating(object sender, PopulatingEventArgs e)
{
if (PopulatingAsync == null) return;
PopulatingAsync(this,new PopulatingAsyncEventArgs(PopulateComplete,e.Parameter));
// cancel the population of the auto complete box
e.Cancel = true;
}
}
PopulatingAsyncEventArgs class is consisted of a Parameter and an action which will be called on ViewModel side when population of items is complete. It basically calls a PopulateComplete Action of AutoCompleteBox
public class PopulatingAsyncEventArgs:EventArgs
{
public PopulatingAsyncEventArgs(Action complete,string parameter)
{
PopulateComplete = complete;
Parameter = parameter;
}
public string Parameter { get; private set; }
public Action PopulateComplete { get; private set; }
}
On viewmodel side you do it something like this:
public void FilterPartners(PopulatingAsyncEventArgs args)
{
Context.Load(Context.GetPartnersQuery().Where(x => x.Name.Contains(args.Parameter)),
x =>
{
PartnerList.Clear();
PartnerList.AddRange(x.Entities);
args.PopulateComplete();
}
, null);
}
And on the view side you hook it up like this:
<AltiControls:AltiAutoCompleteBox
Micro:Message.Attach="[Event PopulatingAsync]=[Action FilterPartners($eventArgs)]"
SelectedItem="{Binding Partner,Mode=TwoWay}"
ItemsSource="{Binding ElementName=ClaimFormUc, Path=DataContext.PartnerList}"/>
The important thing is the Message.Attach part.
And that’s basicall it! Next time you want to use this AutoCompleteBox, you just define the service and hook it up with message.Attach! And it has that yummy bubbling through visual tree built in and all!
Till next time..:)
Ok, i get this error
Index was outside the bounds of the array.
and i always bang my head up the wall what to do with it. Anyway, it’s the enum!
I have a mapping where enums are stored in referenced table. If the table’s empty, my code generator generates an empty enum.
Empty enum then causes this exception.
And that’s basically it! Now off to change that whole enum deal into something smarter….
I needed a classic “where guid in” type of query to do (though i’m not sure if i should be thinking in that way when using linq:) so i poked around a bit, but couldn’t find anything good. So here it is:
public static IQueryable<T> WhereIn<T,TMember>(this IQueryable<T> query, Expression<Func<T, TMember>> member, IList<TMember> inList) where T : BaseEntity
{
if (inList.Count() == 0) return query.Where(x => false);
Expression<Func<TMember, bool>> expr = x => Enumerable.Contains(inList,x);
var method = ((MethodCallExpression)expr.Body).Method;
var expression = Expression.Call(null, method, ((MethodCallExpression)expr.Body).Arguments[0], member.Body);
var lambda = Expression.Lambda<Func<T, bool>>(expression, "inLambda", member.Parameters);
return inList.Count() > 0 ? query.Where(lambda) : query.Where(x => false);
}
A classic example of usage would be something like this
Repository.Query<Partner>().WhereIn(x => x.Guid, partnerGuidList))
We are basically selecting partners by partnerguid, it’s very useful for paging and such.
An ispiration for this type of solution came from the this blog post for catching future values. Hope this helps!
Man, why do i always have to learn the stuff the hard way?
I was doing a deployment of my RIA solution to the production server after a visual studio update (not sure if it’s connected or not), and i would start getting these strange errors when trying to log in:
There was a failure using the default ‘ProfileProvider’. Please make sure it is configured correctly. The database ‘C:\INETPUB\WWWROOT\APP_DATA\ASPNETDB.MDF’ cannot be opened because it is version 661. This server supports version 655 and earlier. A downgrade path is not supported.
Cannot open user default database. Login failed.
Login failed for user ‘NT AUTHORITY\NETWORK SERVICE’. InnerException message: The database ‘C:\INETPUB\WWWROOT\APP_DATA\ASPNETDB.MDF’ cannot be opened because it is version 661. This server supports version 655 and earlier. A downgrade path is not supported.
Cannot open user default database. Login failed.
Login failed for user ‘NT AUTHORITY\NETWORK SERVICE’.
The trouble is, i wasn’t using aspnedb.mdf at all. I figured i would just delete the database and everything would be fine. Then i got this:
Load operation failed for query ‘Login’. Access to the path ‘C:\inetpub\wwwroot\App_data’ is denied.
Seems that asp.net membership provider is trying to create that ASPNETDB.MDF file on the server although i’ve set it up (or so i thought) to use my database.
My web.config part (the one that’s important) looks like this:
<profile>
<providers>
<add name="CustomizedProfileProvider" type="System.Web.Profile.SqlProfileProvider"
connectionStringName="database"
applicationName="/AltiFin"/>
</providers>
<properties>
<add name="FriendlyName"/>
<add name="BranchGuid" type="System.Nullable`1[System.Guid]"/>
</properties>
</profile>
To cut the long story short, you should ALWAYS set defaultProvider on all the asp.net providers like this:
<profile defaultProvider="CustomizedProfileProvider"> <!--This part on the left! -->
<providers>
<clear/> <!--you should also put this on the left -->
<add name="CustomizedProfileProvider" type="System.Web.Profile.SqlProfileProvider"
connectionStringName="database"
applicationName="/AltiFin"/>
</providers>
<properties>
<add name="FriendlyName"/>
<add name="BranchGuid" type="System.Nullable`1[System.Guid]"/>
</properties>
</profile>
Otherwise asp.net membership will try to create a default database. just to be safe, you should also put the
Well, i ran into an issue while inserting multiple records which is nicely described in my post on a forum:
http://forums.silverlight.net/forums/t/222783.aspx
Long story short, ria services doesn’t take care of the ordering of the items when calling insert methods for entities, ie it could easily call insert on child before parent. This problem doesn’t show itself when using Composition attribute, but it’s not like you want to use that attribute every time, so if you’re not using you’re pretty much leaving yourself at the mercy of your ORM. And Nhibernate is pretty crude sometimes in some respects, one of them being the data insertion.
If you don’t explicitly establish 2-way connection, NHibernate is just going to insert the data in the order you persisted it. Which in combination with RIA Service’s indifference is BAD.
I won’t give any sample project because it’s a bit difficult to extract plus i don’t have time, but this is the basic principle.
I decided to make a workaround on the Service level. What i decided to do is make an override on domainservice’s Submit method.
public override bool Submit(ChangeSet changeSet)
{
InsertList = changeSet.ChangeSetEntries
.Where(x => x.Operation == DomainOperation.Insert && x.Entity is BaseEditModel)
.Select(y => new InsertEntry(false, y)).ToList();
foreach (var entry in InsertList)
{
if (!entry.Inserted)
{
PersistEntry(entry, InsertList);
}
}
return base.Submit(changeSet);
}
BaseEditModel is a base class i use for all the Presentation models, you should use your own base clas, the point is that you just take entites which are getting inserted.
InsertEntry class is pretty basic:
private class InsertEntry
{
public InsertEntry(bool inserted, ChangeSetEntry entry)
{
Inserted = inserted;
Entry = entry;
}
public ChangeSetEntry Entry { get; set; }
public bool Inserted { get; set; }
}
It’s used basically for keeping track of already persisted entities.
The meat of the solution is in the PersistEntry method:
private void PersistEntry(InsertEntry insEntry, IEnumerable<InsertEntry> insertList)
{
if (insEntry.Inserted) return;
var entPm = (BaseEditModel)insEntry.Entry.Entity;
if (entPm == null) return;
if (insEntry.Entry.Associations != null)
//iterate through associations of the entry
foreach (var ass in insEntry.Entry.Associations)
{
//test if the association is a foreign key association
if (IsForeignKey(entPm.GetType(), ass.Key))
{
//find the entity from association in the insertList
var entryTup = insertList.FirstOrDefault(x => x.Entry.Id == ass.Value[0]);
//if it's found and it's not already inserted, insert it
if (entryTup != null && !entryTup.Inserted)
//the insertion is done recursively
PersistEntry(entryTup, insertList);
}
}
//since i'm using the presentation model, i first have to find the corresponding domain entity model
var entType = GetEntType(entPm);
var ent = (BaseEntity)Activator.CreateInstance(entType);
ent.Guid = entPm.Guid;
//sets the entity properties from presentation model
entPm.SetEntity(ent, this);
//and finally persist it
Repository.Persist(ent);
//set the inserted property to true so that the same entity doesn't get inserted 2 times
insEntry.Inserted = true;
}
The IsForeignKey method basically tests the association of a property for foreignkey bool value
private static bool IsForeignKey(Type entPmType, string propName)
{
var propInfo = entPmType.GetProperties().FirstOrDefault(x => x.Name == propName);
if (propInfo == null) return false;
var assAtt = propInfo.GetCustomAttributes(typeof(AssociationAttribute), false)[0] as AssociationAttribute;
return assAtt != null && assAtt.IsForeignKey;
}
I documented the code the best i could, so i hope you get the picture. The only thing left to do is to modify the InsertEntity methods so that it doesn’t persist anything, because we already persisted it.
protected void InsertEntity(TPm entityPm)
{
var entity = Repository.GetItem(entityPm.Guid);
ChangeSet.Associate(entityPm, entity, UpdateEntityPmAttributes);
}
So the previous method is used mostly to do the Association, we already did the persistion stuff in the submit method.
And that’s basically it, we did the insertion in the order which respects associations, and therefore you don’t get those nasty foreign key sql exceptions. Hope it’s well explained and you’ll find the way to use it on your example. It can easily be used on Entity domain models too.
Hope this helps!
There’s really something scary about when the whole foundation of your work starts to tremble because of some things not directly under your control. That’s what scares me the most about Microsoft being such a dominant force int the .NET ecosystem. Don’t get me wrong, there’s a LOT of good stuff happening because of Microsoft, and guys over there can be really helpful, but the latest troubles i encountered with the dreaded memory leakage really shook me up.
Exhibit A:
http://forums.silverlight.net/forums/t/171739.aspx
Long story short, there was a severe memory leak caused by inline datatemplates, and it’s one of the issues that, by far, got the most attention. What’s scary about it, it took 10 WHOLE MONTHS to get the issue sorted. The patch that took care of the issue was released just this week. Granted, you could go around it by not using inline templates (i’m not to fond of them anyway) but it really hurt a lot of people. That thread alone got 14 million view (!?) and it turned into a debate about Microsoft’s policies and release schedules.
Exhibit B:
http://www.baud.cz/blog/memory-leak-in-silvelright-with-inotifydataerrorinfo
You have a nice example there how to fill up your whole memory in 50 clicks of a button:) I won’t go much into detail here except that i did my own workaround for this problem, which only partially solves the problem, if you have the INotifDataErrorInfo under control. You can find a partial solution on the following link (won’t help with ria services entites though)
The good thing about this one is that it only REALLY affects you if you stay on one screen to long and refresh you data often. If you navigate between lists and grids, the entities will be released along with the controls so for most of the use cases it doesn’t really present THAT big of a problem. Still, you’d have to wonder, this issue has been known of for a looong time, and it’s still not resolved!
As for RIA Services users, this one could be easily resolved if only we had power to modify the abstract Entity class and put the code from the upper link insted of the strong referenced event. That is, if only RIA Services was open sourced, this issue would probably be solved in 5 days:).
UPDATE
Got an answer from kyle on this issue, still haven’t tried it though. This is a workaround:
http://forums.silverlight.net/forums/t/220074.aspx
Exhibit C:
Bing maps silverlight control
Well, i started using this control for my application, only to realize that this darn thing just likes to sit in memory. Actually, this was the first memory leak that i encountered in my application, so i decided to solve it by separating the control into a separate view and assigning it a dedicated viewmodel and making the whole thing a singleton. The good thing about the approach is that the map doesn’t get loaded repeatedly every time i navigate to page owning it, and it can be reused really easily. The bad thing is that it still locks the last page holding the map in the memory, but only the last one. I think that issue could easily be resolved, but i couldn’t be bothered honestly. In the end it turned out for the better.
Sky falling down on me
What really scared me is that once i started searching for the solution for that last problem, i couldn’t believe the sheer number of problems related to memory leaks haunting the whole silverlight ecosyste. I got scared shitless, so much that i really felt cheated because of investing into this technology at first. But once the passions settled down, i started thinking clearly and eliminated one problem at a time. There are still some weird problems with my application sometimes, not really memory related, but they’re no biggie. I just hope there’ll be more investment in bug fixing than feature adding.
This made me even more firm about my decision to go with open source technologies wherever possible (nhibernate, autofac, caliburn, etc…) because i just CAN’T rely on a 10 months bug repair cycle for my business.
Oh, i got this from the thread mention before, just a quick summary of some of the biggest pitfalls concerning memory leaks. Some of them might already be fixed, but be sure to check for yourselves.
1) Move inline DataTemplates to resources
2) Don’t reassign the ObservableCollection
3) Use one ContextMenu for a control like ItemsControl rather than a contextmenu in the ItemTemplate DataTemplate. Capture the current item datacontext with a MouseRightButtonDown handler for the ItemTemplate DataTemplate.
4) WCF RIA services users are probably running into this one: There is a memory leak with INotifyDataErrorInfo which Ria entities implement. I stopped binding views directly to RIA entities and instead use the approach described by this author: http://connect.microsoft.com/VisualStudio/feedback/details/562236/memory-leak-garbage-collection-issue-with-binding-to-subentities-in-silverlight-4-rtw
5) This might make a difference: Rather than bind to ObservableCollection
6) This is a kludge: After 10 minutes of inactivity I autosave the data, and after 60 minutes of inactivity I logout the user and reload the silverlight app. Inspired by code from http://bryantlikes.com/SearchView.aspx?q=logout and http://stackoverflow.com/questions/552756/can-silverlight-initiate-page-refreshes .
Ok, i’m putting this here so i can get back to this later and finally write this one down, along with some nice examples and code samples.
This is a sequal of on of previous blog posts. It turned out to be REALLY beneficial, for a number of reasons so stay tuned…. Now onward with the topic of the day….
