views:

48

answers:

2

Hi

Trying to understand this binding process of the WPF.

See the code at the bottom.

In my "viewmodel", see the code at the bottom, i have an observable collection that is populating the listview with the items. Thats the one that contains a path called symbol to set the selected index in the combobox. Now my problem is that i need to populate the combobox from another list before its added to the listview (some default values). Since i just started with WPF i thought that perhaps you can use 2 different ObservableCollections in the same class to achieve this but that didn't work. So how can i populate the datatemplate with the default values before they are bound/added to the listview?

This is what i use to populate the listview, see the viewmodelcontacts at the bottom.

I also tried to add another class with a new observablecollection that i could use in my combobox, but i didn't get that to work either. The data that should be populated comes from a XML file located as a resource in my app.

Another question, is it possible to add commands to images? or are commands only available from controls that inherit from the button_base class? I wanted to use an image next to an element and when the user clicked on that image they would remove the element.

  • From the answer below, is it possible without adding a button since i don't want the button feeling (for instance when hovering and clicking)*

Window.xaml:

<r:RibbonWindow x:Class="Onyxia_KD.Windows.ContactWorkspace"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"        
    Title="Contact card" ResizeMode="NoResize" Height="600" Width="600"
    Background="White">
<r:RibbonWindow.Resources>
    <DataTemplate x:Key="cardDetailFieldTemplate">
        <TextBox Text="{Binding Path=Field}" MinWidth="150"></TextBox>
    </DataTemplate>
    <DataTemplate x:Key="cardDetailValueTemplate">
        <TextBox Text="{Binding Path=Value}" MinWidth="150"></TextBox>
    </DataTemplate>
    <DataTemplate x:Key="cardDetailSymbolTemplate">
         <!-- Here is the problem. Populating this with some default values for each entry  before the selectedIndex is bound from the datasource -->
        <ComboBox SelectedIndex="{Binding Path=Symbol}" DataContext="_vmSettings" ItemsSource="{Binding Symbols}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
            <ListViewItem Padding="0,3,0,3" Content="{Binding Path=Value}" />                                    
        </ComboBox>
    </DataTemplate>
    <DataTemplate x:Key="cardDetailCategoryTemplate">
        <ComboBox SelectedIndex="{Binding Path=Category}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
            <!--same as the combobox above but categories instead of symbols-->                
        </ComboBox>
    </DataTemplate>
</r:RibbonWindow.Resources>
...
<ListView ItemsSource="{Binding ContactData}" Foreground="Black" SelectionMode="Single" x:Name="cardDetailList" KeyDown="cardDetailList_KeyDown">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="Category" Width="auto" CellTemplate="{StaticResource cardDetailCategoryTemplate}"></GridViewColumn>
                            <GridViewColumn Header="Field" Width="auto" CellTemplate="{StaticResource cardDetailFieldTemplate}"></GridViewColumn>
                            <GridViewColumn Header="Symbol" Width="70" CellTemplate="{StaticResource cardDetailSymbolTemplate}"></GridViewColumn>
                            <GridViewColumn Header="Value" Width="auto" CellTemplate="{StaticResource cardDetailValueTemplate}"></GridViewColumn>                                
                        </GridView>
                    </ListView.View>                        
                </ListView>

Code behind:

    private ViewModelContacts _vm;  

    public ContactWorkspace()
    {
        InitializeComponent();

        _vm = new ViewModelContacts();            
        this.DataContext = _vm;

    }

    private void Run_MouseUp(object sender, MouseButtonEventArgs e)
    {
        _vm.AddNewDetail();
    }

    private void Image_MouseUp(object sender, MouseButtonEventArgs e)
    {
        _vm.AddNewDetail();
    }

    private void cardDetailList_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Delete)
        {
            if (MessageBox.Show("Are you sure that you want to delete this?", "Warning!", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
            {
                _vm.RemoveDetail(cardDetailList.SelectedIndex);
            }
        }
    }

ViewModelContacts:

public ObservableCollection<ContactCardData> ContactData { get; set; }               

    public ViewModelContacts()
    {

        ContactData = new ObservableCollection<ContactCardData>();            
        Populate();
    }

    private void Populate()
    {
        ContactData.Add(new ContactCardData("Test", 0, 0, "Value123"));
        ContactData.Add(new ContactCardData("Test2", 1, 1, "Value1234"));
        ContactData.Add(new ContactCardData("Test3", 2, 2, "Value1235"));
        ContactData.Add(new ContactCardData("Test4", 3, 3, "Value12356"));            
    }

    public void UpdateNode()
    {
        ContactData.ElementAt(0).Value = "Giraff";
    }

    public void AddNewDetail()
    {
        ContactData.Add(new ContactCardData());
    }

    public void RemoveDetail(int position)
    {
        ContactData.RemoveAt(position);
    }

ViewModelContactData:

public class ContactCardData : DependencyObject
{
    public int Category
    {
        get { return (int)GetValue(CategoryProperty); }
        set { SetValue(CategoryProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Category.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CategoryProperty =
        DependencyProperty.Register("Category", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));

    public string Field
    {
        get { return (string)GetValue(FieldProperty); }
        set { SetValue(FieldProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Field.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FieldProperty =
        DependencyProperty.Register("Field", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));

    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));

    public int Symbol
    {
        get { return (int)GetValue(SymbolProperty); }
        set { SetValue(SymbolProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Symbol.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SymbolProperty =
        DependencyProperty.Register("Symbol", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));

    public ContactCardData()
    {
    }

    public ContactCardData(string field, int category, int symbol, string value)
    {
        this.Symbol = symbol;
        this.Category = category;
        this.Field = field;
        this.Value = value;
    }
}
A: 

For the last question, you can use a button and template it to include an image.

Veer
A: 

Define your Window class as follows (explanation will be later):

<Window x:Class="TestCustomTab.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestCustomTab="clr-namespace:TestCustomTab" Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <DataTemplate x:Key="workingTemplate">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                    <ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <Ellipse Grid.Column="0" Fill="Red" Height="5" Width="5"/>
                                    <TextBlock Grid.Column="1" Text="{Binding}" />
                                </Grid>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                </Grid>
            </DataTemplate>

            <DataTemplate x:Key="personalTemplate">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                    <ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <Ellipse Grid.Column="0" Fill="Green" Height="5" Width="5"/>
                                    <TextBlock Grid.Column="1" Text="{Binding}" />
                                </Grid>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                </Grid>
            </DataTemplate>

            <TestCustomTab:ContactDataByTypeTemplateSelector x:Key="contactDataByTypeTemplateSelector"/>
        </Window.Resources>
        <Grid x:Name="grid">        
            <ListView ItemsSource="{Binding ContactDataCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TestCustomTab:Window1}}}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Column" Width="200" CellTemplateSelector="{StaticResource contactDataByTypeTemplateSelector}">

                        </GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>
        </Grid>
    </Window>

Lets assume that your ContactData looks as follows:

public class ContactData : INotifyPropertyChanged
    {
        private string _name;
        private int _homePhone;
        private int _mobilePhone;
        private string _value;
        private ContactDataType _dataType;

        public ContactData()
        {
        }

        public ContactData(string name, int homePhone, int mobilePhone, string value, ContactDataType dataType)
        {
            _name = name;
            _dataType = dataType;
            _value = value;
            _mobilePhone = mobilePhone;
            _homePhone = homePhone;
        }

        #region Implementation of INotifyPropertyChanged

        public ContactDataType DataType
        {
            get { return _dataType; }
            set
            {
                if (_dataType == value) return;
                _dataType = value;
                raiseOnPropertyChanged("DataType");
            }
        }

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                raiseOnPropertyChanged("Name");
            }
        }

        public int HomePhone
        {
            get { return _homePhone; }
            set
            {
                if (_homePhone == value) return;
                _homePhone = value;
                raiseOnPropertyChanged("HomePhone");
            }
        }

        public int MobilePhone
        {
            get { return _mobilePhone; }
            set
            {
                if (_mobilePhone == value) return;
                _mobilePhone = value;
                raiseOnPropertyChanged("MobilePhone");
            }
        }

        public string Value
        {
            get { return _value; }
            set
            {
                if (_value == value) return;
                _value = value;
                raiseOnPropertyChanged("Value");
                raiseOnPropertyChanged("Symbols");
            }
        }

        public ReadOnlyCollection<char> Symbols
        {
            get
            {
                return !string.IsNullOrEmpty(_value) ? new ReadOnlyCollection<char>(_value.ToCharArray()) : new ReadOnlyCollection<char>(new List<char>());
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void raiseOnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

It has DataType property of type ContactDataType which is enum:

public enum ContactDataType
    {
        Working,
        Personal
    }

In ability to have different DataTemplates for the same entities differentiated by some feature you need to use DataTemplateSelector. The technique is in inheriting from DataTemplateSelector and overriding SelectTemplate method. In our case:

public class ContactDataByTypeTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var contactData = item as ContactData;
            var control = container as FrameworkElement;
            if (contactData != null & control != null)
                switch (contactData.DataType)
                {
                    case ContactDataType.Working:
                        return control.TryFindResource("workingTemplate") as DataTemplate;
                    case ContactDataType.Personal:
                        return control.TryFindResource("personalTemplate") as DataTemplate;
                    default:
                        return base.SelectTemplate(item, container);
                }

            return base.SelectTemplate(item, container);
        }
    }

Here is Window1 class in code behind:

public partial class Window1 : Window
    {
        private ObservableCollection<ContactData> _contactDataCollection = new ObservableCollection<ContactData>();

        public Window1()
        {
            ContactDataCollection.Add(new ContactData("test1", 0, 1, "value1", ContactDataType.Working));
            ContactDataCollection.Add(new ContactData("test2", 0, 1, "value2", ContactDataType.Working));
            ContactDataCollection.Add(new ContactData("test3", 0, 1, "value3", ContactDataType.Working));
            ContactDataCollection.Add(new ContactData("test4", 0, 1, "value4", ContactDataType.Personal));
            ContactDataCollection.Add(new ContactData("test5", 0, 1, "value5", ContactDataType.Personal));

            InitializeComponent();

        }


        public ObservableCollection<ContactData> ContactDataCollection
        {
            get { return _contactDataCollection; }
        }
    }

Now explanation:

  1. We created some class that we need to represent to user (ContactData) and let him to have feature - ContactDataType.

  2. We created 2 DataTemplates in resources (x:Key is important) for ContactDataType.Working and ContactDataType.Personal

  3. We created DataTemplateSelector to have ability switch templates by feature.

  4. In our first GridViewColumn we defined CellTemplateSelector and bind to it our ContactDataByTypeTemplateSelector.

  5. In runtime whenever the collection changes ContactDataByTypeTemplateSelector select to us template based on item feature and we may have any number of templates for any number of defined features.

Notice: change TestCustomTab for your namespace.

Eugene Cheverda
Hi, thanks for the response. I didn't explain myself 100% it seems.I added my XAML for the listview and my code, what i didn't manage to get working was the symbols. I have no idea what to bind that to. I would like to add all the predefined xml content to an external class and use that for all the predefined databinding. But how can i bind a combobox to 2 datacontexts at the same time? One for applying the current selected index and one for populating the combobox before its shown with the default values.
Patrick