|
Thanks for that, something for the weekend experimentation!
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Still trying to grasp MVVM here. I have 4 controls I display from my main form.
Basically
<DockPanel LastChildFill="True">
<views:RibbonControl x:Name="mainRibbon" DockPanel.Dock="Top" />
<controls:StatusBarControl x:Name="mainStatusBar" DockPanel.Dock="Bottom" />
<controls:NavigationControl x:Name="mainNavigation"
DockPanel.Dock="Left"
Visibility="Hidden" />
<controls:DisplayContactsControl x:Name="mainContactsDisplay"
DockPanel.Dock="Left"
Visibility="Hidden" />
</DockPanel>
How can I update say messages for the status bar when things occur inside one of the other controls. Lets say On the RibbonControl there is a button to open the database. The code to open it would include references to displaying a message on the statusbar control that the form is loading and also update the title of the form to display the path of the database, name and title of the application. Now I can do this in regular WPF but their is code behind I'm trying to understand how to do this in MVVM.
modified 24-Jul-12 11:05am.
|
|
|
|
|
Most people doing this would use either a Messenger or a Mediator. These are common techniques that play nicely with MVVM.
|
|
|
|
|
Using Binding on your StatusBarControl will helps you.
Christian Amado
MCITP | MCTS | MOS | MTA
DCE 0★ 1★ 2★ 3★ 4★ 5★
Bronze level MVA
Olimpia ☆ ★★★
|
|
|
|
|
I'm not sure I understand. How would binding allow me to display something like, Management Mode - Contacts Manager - Editing Contacts? Or Management Mode - Contacts Manager - Create New Contact?
|
|
|
|
|
Bind the controls text property to a property (VMProp) in your VM, update VMProp with the status you need to display. That update is driven by the processes in the VM that are initiate by the user.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
I think I understand. I've noticed that MVVM is basically Bind everything lol.
|
|
|
|
|
Alisaunder wrote: MVVM is basically Bind everything
You have that absolutely correct. No more code to manipulate the UI directly, everything is in the VM, or converters etc
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Unless the OP is using a single VM, you have missed important information out. How is another unrelated control going to be updated? If you see my answer, you can see the common mechanisms which answers the question the OP actually asked.
|
|
|
|
|
I'm trying to add images to a column in a WPF datagrid which are read from a folder and not added as a static resource. The datagrid shows the path to the image in debug but the cell is empty.
XAML:
<DataGrid
Name="dgDevices"
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding Scanners}"
SelectionChanged="dgDevices_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Device Image">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="imgScanner" Source="{Binding Path=ImageFile}" Height="100" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Details" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap">
<TextBlock Text="{Binding Path=FriendlyName}"/>
<Hyperlink NavigateUri="{Binding Path=URL}" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock Text="{Binding Path=URL}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code that describes the objects for the datagrid:
public class Scanners : INotifyPropertyChanged
{
#region dongleNum related info
private UInt32 thisIPAddress;
public UInt32 IPAddress
{
get { return thisIPAddress; }
set { thisIPAddress = value; NotifyPropertyChanged("IPAddress"); }
}
private UInt64 thisMACAddress;
public UInt64 MACAddress
{
get { return thisMACAddress; }
set { thisMACAddress = value; NotifyPropertyChanged("MACAddress"); }
}
#endregion
#region Device related info
private string thisPortName;
public string PortName
{
get { return thisPortName; }
set { thisPortName = value; NotifyPropertyChanged("PortName"); }
}
#endregion
#region Driver related info
private string thisDeviceGuid;
public string DeviceGuid
{
get { return thisDeviceGuid; }
set { thisDeviceGuid = value; NotifyPropertyChanged("DeviceGuid"); }
}
#endregion
#region Presentation related info
private string thisImageFile;
public string ImageFile
{
get { return thisImageFile; }
set { thisImageFile = value; NotifyPropertyChanged("ImageFile"); }
}
private string thisFriendlyName;
public string FriendlyName
{
get { return thisFriendlyName; }
set { thisFriendlyName = value; NotifyPropertyChanged("FriendlyName"); }
}
private Uri thisURL;
public Uri URL
{
get { return thisURL; }
set { thisURL = value; NotifyPropertyChanged("URL"); }
}
#endregion
#region Constructors
public Scanners(string newFriendlyName, string newImageFile)
{
FriendlyName = newFriendlyName;
ImageFile = newImageFile;
}
public Scanners(sxWrapper.sxDeviceInfo DeviceInfo, string newFriendlyName, Uri newURL, string newImageFile, string newDeviceGuid)
{
MACAddress = DeviceInfo.MACAddress;
IPAddress = DeviceInfo.IPAddress;
PortName = DeviceInfo.PortName;
FriendlyName = newFriendlyName;
URL = newURL;
ImageFile = newImageFile;
DeviceGuid = newDeviceGuid;
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class DeviceViewModel
{
private static ObservableCollection<Scanners> devices = null;
public static ObservableCollection<Scanners> Devices
{
get { return devices; }
set { devices = value; }
}
public DeviceViewModel()
{
this.Initialize();
}
private void Initialize()
{
Reset();
}
public static void Reset()
{
devices = new ObservableCollection<Scanners>();
}
public static void Add(Scanners Scanner)
{
devices.Add(Scanner);
}
}
The Path "ImageFile" in the XAML is a string which is the image's location. i.e. "Images\image1.bmp" The folder Images is located in the target folder containing bmp images. I can only display images in the cells if the images are added as resources in the project. I read a string from an INI class that describes the file name in the Images folder. There are other strings read from the INI file that appear in the other column. Only the images don't appear.
I don't what I am doing wrong, please help.
|
|
|
|
|
Try this (works on Silverlight too):
Create a converter class:
public sealed class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
try
{
return new BitmapImage(new Uri((string)value));
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your XAML you must add a new resource and implement a convert in your image element inside the CellTemplate. Remember to add the extension namespace on your xaml file header.
<DataGrid
Name="dgDevices"
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding Scanners}" SelectionChanged="dgDevices_SelectionChanged"> <DataGrid.Resources>
<extension:ImageConverter x:key="imgConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Device Image">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="imgScanner" Source="{Binding Path=ImageFile,
Converter={StaticResource imgConverter}}" }" Height="100" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Details" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap">
<TextBlock Text="{Binding Path=FriendlyName}"/>
<Hyperlink NavigateUri="{Binding Path=URL}" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock Text="{Binding Path=URL}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Hope its helps
Christian Amado
MCITP | MCTS | MOS | MTA
DCE 0★ 1★ 2★ 3★ 4★ 5★
Bronze level MVA
Olimpia ☆ ★★★
|
|
|
|
|
I found the following as a source of help towards the solution:
public class ImageConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri((string)value, UriKind.RelativeOrAbsolute));
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
The path to the image is Images\File.bmp. I tried changing the formatting of the string to create a proper Uri, but the image STILL does not appear in the cell
I found a solution which helped in better creating the path to be in the proper form of a Uri. What follows IS the solution:
public class ImageConverter : IValueConverter
{
public static BitmapImage CreateBMImage(string path)
{
BitmapImage bi = new BitmapImage();
try
{
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnDemand;
Uri baseUri = new Uri(Application.ResourceAssembly.Location);
bi.UriSource = new Uri(baseUri, path);
bi.EndInit();
}
catch
{
return null;
}
return bi;
}
public static Image CreateImage(string path)
{
Image img = new Image();
img.Source = CreateBMImage(path);
return img;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return CreateBMImage((string)value);
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
modified 23-Jul-12 10:34am.
|
|
|
|
|
The subject is somewhat of a misnomer, I have a category combobox that governs what defects are available, but that is it. The category information should not be saved. Easier to show than say:
Here is the relevant ViewModel:
public Category SelectedCategory
{
get { return _selectedCategory; }
set
{
if (value == null) return;
_selectedCategory = value;
OnPropertyChanged("SelectedCategory");
LoadDefects(_selectedCategory.id);
}
}
And the View:
<ComboBox
Grid.Row="2"
Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.AllCategories}"
DisplayMemberPath="name"
SelectedValuePath="id"
SelectedValue="{Binding SelectedItem.Defect.categoryId}"
SelectedItem="{Binding SelectedCategory}" />
<ComboBox
Grid.Row="3"
Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.AllDefects}"
DisplayMemberPath="name"
SelectedValuePath="id"
SelectedValue="{Binding SelectedItem.defectId}"/>
What is happening is that instead of just saving the SelectedItem (Observation class not shown) it is changing SelectedItem.Defect.CategoryId, which I don't want. I only want SelectedItem to be saved. Save routine is pretty simple:
ViewModel:
private void Save()
{
_con.SaveChanges();
}
Thoughts?
Cheers, --EA
|
|
|
|
|
I think you're confusing how the various ComboBox properties work:
DisplayMemberPath = Choose how to display the item, equivalent to making an ItemTemplate with a TextBlock that binds to said path.
SelectedValuePath = Use with the SelectedValue property when the items in the list aren't the same as the property you're binding to. For example, the items in your list may have a Name and an ID, so you set the SelectedValuePath to "ID" and bind SelectedValue to the "SelectedID" property of your model.
SelectedValue = See previous
SelectedItem = Use this when the items in your list are the actual items in your model. Do not combine it with SelectedValue or SelectedValuePath.
|
|
|
|
|
I am certainly confused about the various properties and what combination to use them in. I understand what you have written, though.
Ideally, I would just have SelectedCategory drive that combobox as I need the INotifyProperyChanged to work to populate the defect list. But I also need that combobox to have an initial value based on the currently selected item.
So how do I accomplish this? I need the selected value to be SelectedItem.Defect.CategoryId, but I also need to notify my viewmodel when particular property is changed. Which one needs to be removed and how do I still accomplish what I need?
|
|
|
|
|
Well, let's look at your particular case...
Since your model property seems to match the item in the list (Correct me if I'm wrong), you don't need to worry about the SelectedValue/Path properties. So take those out of your first combo, and that one should work just fine.
As for the second one, you want its ItemsSource to change based on the SelectedCategory, right? Whatever your LoadDefects function is modifying, that's what you want the second combo's ItemsSource to bind to.
You should also have a SelectedDefect property (Or something of the sort), that the second combobox should bind to in its SelectedItem property. The LoadDefects function can set the SelectedDefect to an initial guess when it updates the collection.
So in your case, I don't think you really need SelectedValue/Path at all.
|
|
|
|
|
You were right, did not need all of those properties. I see your point about the SelectedDefect, but it is ok to leave it blank when the user changes the category as I am relying on IDataErrorInfo to ensure that the user does not leave it blank.
This was the final product (For now):
public Observation SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value)
return;
_selectedItem = value;
_selectedCategory = _con.Categories.Where(c => c.id == value.Defect.categoryId).FirstOrDefault();
OnPropertyChanged("SelectedItem");
OnPropertyChanged("SelectedCategory");
LoadDefects(_selectedCategory.id);
}
}
public Category SelectedCategory
{
get { return _selectedCategory; }
set
{
if (value == null) return;
_selectedCategory = value;
OnPropertyChanged("SelectedCategory");
LoadDefects(_selectedCategory.id);
}
View:
public Observation SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value)
return;
_selectedItem = value;
_selectedCategory = _con.Categories.Where(c => c.id == value.Defect.categoryId).FirstOrDefault();
OnPropertyChanged("SelectedItem");
OnPropertyChanged("SelectedCategory");
LoadDefects(_selectedCategory.id);
}
}
public Category SelectedCategory
{
get { return _selectedCategory; }
set
{
if (value == null) return;
_selectedCategory = value;
OnPropertyChanged("SelectedCategory");
LoadDefects(_selectedCategory.id);
}
Maybe I should change the _selectedCategory to derive from my AllCategories observable collection instead. Either way, this works. Thank you.
Cheers, --EA
|
|
|
|
|
Glad to help
|
|
|
|
|
I created the following button style. I want to set the image's opacity to 50% when the button is disabled. I have the Image.Style code in there, but how do I bind the IsEnabled to the button? See my comment in line.
<Style TargetType="{x:Type controls:FlatButton}"
x:Key="FlatButtonStyle">
<Setter Property="Height" Value="35"/>
<Setter Property="Width" Value="80"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:FlatButton}">
<Border x:Name="TheBorder"
Background="Transparent"
BorderBrush="#6F6F6F"
BorderThickness="0"
CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="{TemplateBinding ImageSource}"
Grid.Row="0"
Grid.Column="0"
Width="16"
Height="16"
Margin="3,0,2,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Fill">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<!--How do I bind to the IsEnabled property on the button?-->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{TemplateBinding Caption}"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="10"
Foreground="#5D5D9D"
FontSize="12"
TextWrapping="WrapWithOverflow"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="TheBorder"
Property="BorderThickness"
Value=".5">
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If it's not broken, fix it until it is
|
|
|
|
|
You just need to add another trigger in the Buttons triggers like so:-
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="TheBorder" Property="BorderThickness" Value=".5"></Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="img" Property="Opacity" Value="50" />
</Trigger>
</ControlTemplate.Triggers>
That assumes that the image control in the style is given a name "img" .
Hope this helps
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
hello all.
I need a tip for good coding. I'm in my first stemps in WPF and i have a srollviewer with button inside that work nice. Now i need to provide another collection of button or another scrollviewer. i mean, the rest of the project dont change, only the scrollviewer.
what is the best way for that?
i hope i explained the issue.
Thanks.
|
|
|
|
|
I am afraid your question is not very clear. Could you try and expand it to make it clearer on what you would like to do.
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
sorry. i will try again.
i have a finished project when i have a scrollviewer with a collection of button inside.
now i want to create the option to open another collection of button in the same project, replacing the old one if the option was select.
in the .cs side i think i can do easly, but maybe, there is a xaml good coding option.
|
|
|
|
|
Create 2 button collections/containers, bind the visibility property to the option control so only 1 is visible based on the user selection.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
I'm totally agree with Mycroft Holmes. Don't forget to create a Converter for Visibility Enumeration.
Regards.
Christian Amado
MCITP | MCTS | MOS | MTA
DCE 0★ 1★ 2★ 3★ 4★ 5★
Bronze level MVA
Olimpia ☆ ★★★
|
|
|
|