Anyway to set 'DataContext' of a row shared by ListView Columns?
That's the best way I could think of to phrase my question, here is the scenario: I have a ListView
bound to a collection of objects. Each of those objects has a property UserID
which is just a reference ID to a User
object. In my ListView
I wish to display multiple properties from both the object and the User
. To do this I have created a class that implements MultiValueConverter
to serve as a lookup table for the user objects. So I use a multibinding which passes the value converter the UserID
and a dictionary look up table which is exposed by the underlying ViewModel.
This all works fine and dandy except I am hoping there is a way I could set the DataContext
or something of the 'row' that the ListView
columns share. In this way I could change my value converter to just return a User object instead of specific properties of the user object. And then I could just bind to the properties of that DataContext
. I don't want to create a new value converter for each User property I wish to expose. The only other way I can think of to do this is by passing property names to value converter and using reflection.
Any ideas? I realize that this DataContext
I am dreaming of is the job of the dataobjects bound to the ListView
's ItemsSource
, but perhaps there is something else I could use too. Attached Properties seem to solve every WPF problem I have so I am betting the solution would have to do with using an AttachedProperty
to create this 'datacontext'
I'm sure someone will tell me to expose the User object from the dataobjects themselves instead of using some backwards method of using user ids and lookup table, BUT, I am doing this for a reason. thanks.
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.Header>User</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock MinWidth="120">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource UserIDConverter}">
<Binding Path="UserID" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}" Path="DataContext.Users"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
The converter:
public class UserIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
int userId = (int)values[0];
IDictionary<int, PhoneUser> table = values[1] as IDictionary<int, PhoneUser>;
if (table.ContainsKey(userId))
{
开发者_开发问答 PhoneUser user = table[userId];
return user.LastName;
//I'd like to just return user !!
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
So, if I understand you correctly, you'd like your converter to just return an entire PhoneUser
object, then have each column decide which property of PhoneUser
to grab?
If you're really going to insist on this convoluted method, I think your reflection idea (pass the property name into the converter and use reflection to return the value) would be best.
That said, I can't resist giving the answer you didn't want to hear (even if it doesn't help you, it might help someone else). Here's what I'd really recommend you do...
Create a class that combines your current object (say it's called
Foo
) and aPhoneUser
.public class FooPhoneUser { Foo Foo { get; set; } PhoneUser User { get; set; } }
Use LINQ to combine these two classes together:
var FooPhoneUsers = from f in Foos join pu in PhoneUsers on f.UserId equals pu.Id select new FooPhoneUser { Foo = f, User = pu };
Get rid of all that binding markup from your
GridViewColumn
, and just put something like this:<TextBlock MinWidth="120" Text={Binding User.LastName} />
or
<TextBlock MinWidth="120" Text={Binding Foo.SomeOtherProp} />
It would be much easier if you could populate your data object with PhoneUser
, instead of just the ID, then you could do:
<DataTemplate>
<StackPanel>
<TextBlock MinWidth="120" Text="{Binding Path="User.FirstName}">
</TextBlock>
<TextBlock MinWidth="120" Text="{Binding Path="User.LastName}">
</TextBlock>
</StackPanel>
</DataTemplate>
The class structure would look something like this:
public class myDataObject //The data object you already have.
{
public string value1;
public string value2;
public PhoneUser User; //currently you have "UserID" here.
}
public class PhoneUser
{
public string FirstName;
public string LastName;
}
If it does not suit you to retrieve all user data when the data object is first loaded, you could use a "Lazy Loading" strategy, like this:
public class myDataObject //The data object you already have.
{
public string UserID;
public string value2;
private PhoneUser _User;
public PhoneUser User
{
get
{
if(_User==null)
_User = getUserFromDatabase(UserID);
return _User;
}
}
}
I believe you could do this without any changes to the structure of your code.
精彩评论