i am using vb.net 2.0 and i would like to set an icon or image in a DataGridViewButtonColumn. I know the class has no member for it. Anyone has an idea?
Best answer is from http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/b0dfb55c-8bad-4c2d-aa72-36d8c138484e/
But as usual, you Google vb.Net and get answers in C#. I use C# to vb.net tool on the developerfusionwebsite to help me convert to vb.net but usually end up changing a fair amount.
Once you have a pointer this one is quite easy really.
In the dataGridView_CellPainting event add something like this
Private Sub InvoiceLinesDataGridView_CellPainting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles InvoiceLinesDataGridView.CellPainting
If e.ColumnIndex = 3 AndAlso e.RowIndex >= 0 Then
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
Dim bmpFind As Bitmap = My.Resources.binoc16_h1
Dim ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 3)
e.Handled = True
End If
End Sub
Just to explain this a little, I wanted to use a resource which is a bitmap so I am converting that to an icon also. This works really well for me and I get a proper button column with an image. The column index is a bit rough as that could change so I was looking to refer using the column name - shouldn't be too hard but you get the idea. SO much easier than the other options I have see around that get you making an extended custom column type.
This really should have just been part of the original control and I cannot fathom why MS crippled the grid so much. I have been trying to use third party controls like Telerik but the originals always seem to be more stable so am now seeing if I can stick with vanilla controls and adding my on extensions where required.
I created a method that can be called in the CellPainting datagridview event.
Public Shared Sub SetImageToDataGridViewButtonColumn_CallInCellPaintingEvent(ByRef img As Bitmap, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs)
e.Paint(e.CellBounds, DataGridViewPaintParts.All & (DataGridViewPaintParts.ContentBackground) & (DataGridViewPaintParts.ContentForeground))
Dim destRect As Rectangle = New Rectangle(e.CellBounds.X + (e.CellBounds.Width - img.Width) / 2, e.CellBounds.Y + (e.CellBounds.Height - img.Height) / 2, img.Width, img.Height)
Dim srcRect As Rectangle = New Rectangle(0, 0, img.Width, img.Height)
e.Graphics.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel)
e.Handled = True
End Sub
In the CellPainting event, call this method passing the image you want on the button and e. Ensure you use some sort of condition to set on the column you need, this example the If specifies column 0. Also notice I have my image in My.Resources:
Private Sub dgv_CellPainting(sender As Object, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles dgv.CellPainting
If (e.ColumnIndex = 0 And e.RowIndex >= 0) Then
SetImageToDataGridViewButtonColumn_CallInCellPaintingEvent(My.Resources.myImg, e)
End If
End Sub
Tips: I found 16x16 png were perfect for my use. You can resize using http://images.my-addr.com/resize_png_online_tool-free_png_resizer_for_web.php
You can try using a DataGridViewImageColumn add attach an event to the grid's CellContentClick event (in the event process only the events comming from the column/columns that is/are images)
Related
So I'm trying to make a listbox with 2 buttons.
Listbox is supposed to display files from a specific folder (I get back to this)
And the two buttons are supposed to be called "Set.." (As in set directory)
and Update (As the list will be refreshed (Something I do every time the Windows Form Runs.
So as of now, when I start my application, and go to the form with the listbox, the listbox is empty. When pressing "Update", the List box picks up files from an address located on my Harddrive (So this is a static address located in my code).
It also finds 7 different extensions (Filetypes), and lists all of them correctly.
My problem is as follows, I want the set Button to open a File Dialog for the user on First-Time Runtime, so the user himself can choose what folder the program "Indexes or Searches" if you will. And then when he runs the application again, and finds the listbox, he can only press Update, and the listbox shows the content of the folder he choose last time.
The Set - button doesn't do anything in my code right now.
Second up, I want each filetype to be labeled or colored with a specific color.
Like; .txt should be blue, .jpg is red, ect..
Running Visual Studio 2013 if that helps.
Also, when checking my code, if you have any suggestions too, how I can improve the code, make it easier, shorter, and just to change things to avoid duplicate codes, please let me know.
Here is from the Design in VS2013
Code:
Private Sub Form_Load(sender As Object, e As EventArgs) Handles Me.Load
FolderBrowserDialog1.SelectedPath = "xxx\xxx\xxx\xxx"
System.IO.Directory.GetCurrentDirectory()
Private Sub updateButtonGame_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles updateButtonGame.Click
If FolderBrowserDialog1.SelectedPath = "xxx\xxx\xxx\xxx" Then
ListFiles(FolderBrowserDialog1.SelectedPath)
End If
End Sub
Private Sub ListFiles(ByVal folderPath As String)
filesListBox.Items.Clear()
Dim fi = From f In New IO.DirectoryInfo(FolderBrowserDialog1.SelectedPath).GetFiles().Cast(Of IO.FileInfo)() _
Where f.Extension = ".z64" OrElse f.Extension = ".nds" OrElse f.Extension = ".BIN" OrElse f.Extension = ".smc" OrElse f.Extension = ".ISO" OrElse f.Extension = ".nes" OrElse f.Extension = ".gb"
Order By f.Extension
Select f
For Each fileInfo As System.IO.FileInfo In fi
filesListBox.Items.Add(fileInfo.Name)
Next
End Sub
Another thing, this is more optional..
My list is completely black, so I choose to have the "Items" in the Listbox turn Light gray.
I played around with something called e.Graphics in hope to achieve Coloring a specific filetype, and it turned ALL items either Black, Red, or whatever I put it to.
But after removing the code, all Items turns into the same color as the Background color of the Listbox. So I can no longer see the elements actually being there, other than the Scroll-bar popping up on the side (Since its many Items in the folder I picked)
Also, I am not that good with coding/visual studio yet, as I started around 1 week ago to date.
Started with VB 2010 and then went to VS2013 to see if I managed to fix some issues, also related to List Box.
If I explained rather poorly, let me know and I'll update with better info.
Project was also first created in VB 2010, and then "Migrated" or opened in VS 2013.
A much, much better way to do this is with a ListView and an ImageList with some standard images for Text, Image, PDF etc in the list, then just set the image key for each item when you add them to the list.
Alternatively, you could emulate the same thing in a listbox (using OwnerDrawFixed) to draw a specified image to indicate the file type. A really good way to implement this is as an ExtenderProvider using code similar to that below as a starting point. As an EP, you can link any cbo or listbox to an image list to provide image cues very much like the ListView works:
The reason you do not see your colored item idiom very often is that whatever colors you pick will not look right on all systems. The more colors, the more likely and more often they wont have enough contrast, be readable etc with the user's color scheme. You also dont need a "Legend" to explain what the colors mean - an image is self explanatory. That said, the DrawItem code would be something like this:
NB: Listbox control is set to OwnerDrawFixed, ItemHeight = 16
Private Sub lb_DrawItem(sender As Object,
e As DrawItemEventArgs) Handles lb.DrawItem
Dim TXT As Color = Color.Black
Dim JPG As Color = Color.Green
Dim PDF As Color = Color.Blue
Dim EXE As Color = Color.Gray
Dim SEL As Color = SystemColors.HighlightText
Dim thisColor As Color = Color.Orange
Dim ndx As Integer = e.Index
' isolate ext ans text to draw
Dim text As String = lb.Items(ndx).ToString()
Dim ext As String = System.IO.Path.GetExtension(text).ToLowerInvariant
' dont do anything if no item being drawn
If ndx = -1 Then Exit Sub
' default
e.DrawBackground()
' color selector
Select Case ext
Case ".jpg"
thisColor = JPG
Case ".txt"
thisColor = TXT
Case ".exe"
thisColor = EXE
Case ".pdf"
thisColor = PDF
End Select
' override color to use default when selected
If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
thisColor = SEL
End If
' render the text
TextRenderer.DrawText(e.Graphics, text, lb.Font, e.Bounds,
thisColor, TextFormatFlags.Left)
' default
e.DrawFocusRectangle()
End Sub
Result:
Works on My SystemTM
Is there any way to include a .swf on my form and also preserve its original transparency? That is, so you can see any controls 'behind it' (obviously in the transparent areas). It's looking unlikely as I understand vb can't control the flash activex control (and hence the control's rendered background?) but wondering whether there's any workarounds?
relevant code thusfar:
Dim flash1 As New AxShockwaveFlash
.....
flash1.Location = New System.Drawing.Point(300, 23)
Me.Controls.Add(flash1)
flash1.Movie = "C:\Users\Steve\Scripts\Projects\CPWizBiz\Assets\Test SWFs\Artwork4.swf"
flash1.Size = New System.Drawing.Size(192, 400)
flash1.Play()
In the absence of any other solutions, here's what I did:
Basically, I just created a new form with a transparetn background. You then overlay this onto your main form (you can link any forms to move and resize with each other etc). Then you set the flash background to match the form transparency key and you're done. Brief, illustrative code:
Public Sub MarqueeFlash_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.TransparencyKey = Color.FromArgb(1, 4, 3)
Me.BackColor = Color.FromArgb(1, 4, 3)
dim FlashCtrl as As New AxShockwaveFlash
Controls.Add(FlashCtrl)
With FlashCtrl
.BGColor = Me.BackColor.R.ToString("X2") & Me.BackColor.G.ToString("X2") & Me.BackColor.B.ToString("X2")
.Movie = IndexedDR(0).Item("File")
.Location = New Point(10,10)
End With
FlashCtrl.Play()
FlashCtrl.Size = New Size(400,300)
end sub
I've got a WinForms Panel control which holds a large number of child controls. Each child is left docked, causing the horizontal width of the contents to grow. The containing Panel has its AutoScroll property set so that you can get to all the contents.
I'm running into a problem when the total width of the contents gets too large. Once you've hit this maximum width, additional content elements are placed on top of existing contents instead of being placed to the right. But, if I resize the Panel after it has done its initial layout, it corrects itself by expanding its logical width and placing each content element in the correct location. How do I get it to layout correctly before the user resizes the window?
Here's a simple example:
Form1.vb
Public Class Form1
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
End Sub
End Class
Form1.Designer.vb
Partial Class Form1
Inherits System.Windows.Forms.Form
Private Sub InitializeComponent()
Me.Panel1 = New System.Windows.Forms.Panel()
Me.SuspendLayout()
'
'Panel1
'
Me.Panel1.AutoScroll = True
Me.Panel1.Dock = System.Windows.Forms.DockStyle.Fill
Me.Panel1.Location = New System.Drawing.Point(0, 0)
Me.Panel1.Name = "Panel1"
Me.Panel1.Size = New System.Drawing.Size(284, 262)
Me.Panel1.TabIndex = 0
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.Panel1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents Panel1 As System.Windows.Forms.Panel
End Class
This is what the window looks like when it first comes up, scrolled nearly to the end so you can see the problem area. Notice that Box 183 to 199 are missing because they are placed on top of each other. This is not right.
This is what the window looks like after you manually resize it, scrolled nearly to the end. The panel fixed itself in response to the resize; the total logical width of the panel was automatically extended enough to hold all the contents. This is what I want it to look like when it first comes up.
I've tried manually setting the location of each box, and I've tried calling PerformLayout() and several other functions. Nothing seems to work. So far I haven't found the magic combination to get the good layout. Does anyone know how to fix this?
Edit:
Here's a screenshot that might make the issue more obvious. I adjusted the box widths and the number of boxes to show the problem better. See how the last box overlaps box 656? Every box from 657 to 700 has the same incorrect location. Turning off docking and setting the location myself doesn't help.
Looks like a bug with the scrolling information. If you call PerformLayout when the Panel is scrolled all the way to the right, it correctly places the controls in the proper place. That requires some code in the OnShown method:
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Panel1.AutoScroll = True
Panel1.SuspendLayout()
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
Panel1.ResumeLayout(False)
End Sub
Protected Overrides Sub OnShown(e As EventArgs)
MyBase.OnShown(e)
Panel1.AutoScrollPosition = New Point(Panel1.HorizontalScroll.Maximum - _
Panel1.HorizontalScroll.LargeChange, 0)
Panel1.PerformLayout()
Panel1.AutoScrollPosition = Point.Empty
End Sub
Of course, having over 200 container controls on the form is never recommended.
AutoScroll is not AutoPositionMyChildren. From MSDN:
When adding controls programmatically to a form, use the AutoScrollPosition property to position the control either inside or outside of the current viewable scroll area.
If you looped thru the controls, to print their location, you's see at some point (probably around 130) that Location.Y becomes fixed at 32767 probably some default unscrolled max. This is also the point they start stacking because they in fact have the same initial location. Some of the code you have makes up for that but it isnt quite right. Once you scroll it, the panel fixes the coords on the child controls.
First, I would suggest that you set Panel1.AutoScrollMinSize to something like {480, 0} so that the HScroll bar appears at design time; this allows you to calc a good height for the boxes which wont cause a VScroll as you add controls.
Dim gb As GroupBox
' only 150 because problem is when (i * width) > 32k
For i As Integer = 0 To 150
gb = New GroupBox
gb.Name = i.ToString ' added
gb.Text = "Box " & i.ToString
gb.Width = 250
' no docking so set the height
gb.Height = Panel1.Bounds.Height - 30 ' trying to avoid the VSCroll
' set location explicitly
gb.Location = NewCtlLocation(Panel1.Controls.Count,
Panel1.AutoScrollPosition.X)
' Dock and Anchor mess up the AutoScroll
'gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
' allow panel to update its scroll positions
Panel1.ScrollControlIntoView(gb)
' not needed; seems to offset something with Dock
' changing ZOrder may not always be desirable
'gb.BringToFront()
' debug illumination
Console.WriteLine("{0} {1} {2}", i.ToString,
Panel1.AutoScrollPosition.X.ToString,
gb.Location.X.ToString)
Next
'Go back to start
Panel1.ScrollControlIntoView(Panel1.Controls("0"))
Location helper so you can tweak gutters or margins (dock replacement):
Friend Function NewCtlLocation(ByVal n As Integer,
ByVal ScrollPosX As Integer) As Point
Const TopMargin As Integer = 5
Const LeftMargin As Integer = 5
Return New Point((n * 250) + ScrollPosX, 0)
End Function
Notes:
I have a vertical scroller which repeatedly adds up to 120 user controls which works well but it does not need/use ScrollControlIntoView and they never stack up like yours do. I suspect maybe because they are smaller. There is also at least a second or two before the next one can be added, which may matter. But, good to know.
It might be possible to use the ControlAdded event of the panel to do something, but it would likely amount to ScrollControlIntoView. Doing it once at the end only doesnt work, so using it as they are added is allowing something to get updated as you go.
With the right fiddling, you might be able to get Dock to work, but I suspect it may be part of the problem such as Height and Left set this way dont update the panel's internal scroll map.
Your boxes actually look narrower than 250 - is autosize on?
Suspend/Resume Layout hurt rather than help - they prevent the control from doing anything about the virtual area being populated. It should happen fast enough that no one will see anything. Results:
Works on My SystemTM
Goal
To be able to paint rows in my DataGridViews in order to help a user better understand what is going on in the application. Take this image for an example:
Light red = Deleted
Yellow = Relationship to the Option Type Select (top left)
Bright Red = Deleted & Related to the Option Type Selected (top left)
Problem
When I go up / down in my Option Types DataGridView, it will paint the Mandatory and Additional sections depending on which Option Type I choose. After doing this for a while (70 or so times), I get the generic error in the GDI.
As you can see in the screenshot above, one of the additional edit buttons are not displayed correctly (this is where the error occurs). It is always on the following line:
ico = Icon.FromHandle(bmpFind.GetHicon)
See complete code below:
Code
Private Sub dgvAdditionalOptions_CellPainting(sender As Object, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles dgvAdditionalOptions.CellPainting
'Dim bmpFind As Bitmap
Dim drRow As HunterManagement.dtConveyorFunctionAdditionalOptionsRow
Dim myOptn As clsOptions
If dgvAdditionalOptions.Columns(e.ColumnIndex).Name = "ColBtnEditAdditional" AndAlso e.RowIndex >= 0 Then
drRow = dsHunterManagement.dtConveyorFunctionAdditionalOptions.FindByPK_ConveyorFunctionAdditionalOption(dgvAdditionalOptions.Rows(e.RowIndex).Cells("PK_ConveyorFunctionAdditionalOption").Value)
myOptn = New clsOptions(CInt(drRow.FK_Option))
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
If Not drRow Is Nothing AndAlso myOptn.InDrawing Then
Using bmpFind As Bitmap = My.Resources.Edit_16x16_2 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
Else
Using bmpFind As Bitmap = My.Resources.Edit_Disabled_16x16_2 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
End If
ElseIf dgvAdditionalOptions.Columns(e.ColumnIndex).Name = "ColBtnDeleteAdditional" AndAlso e.RowIndex >= 0 Then
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
Using bmpFind As Bitmap = My.Resources.Delete_16x16 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 2, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
End If
End Sub
I tried searching this problem and it is all related to 'saving' an image... I am not saving an image though, I am only getting the icon of a BMP image. I must be forgetting to dispose something.
Any ideas?
Typically this error is caused my a gdi+ leak. You need to Dispose the Icon which is a gdi+ resource. Add ico.Dispose once you're done with it.
When using Icon.FromHandle, you need to manually clean it up via DestroyIcon API.
When using this method you must dispose of the resulting icon using
the DestroyIcon method in the Win32 API to ensure the resources are
released.
Refer this answer, Hans gives a workaround for this problem.
Also note that you must Dispose the Bitmap also when you get it from Resources.YourBitmap because Resources.YourBitmap basically creates a new bitmap everytime it is called. Bitmap is also a gdi+ resource, you ought to dispose that also.
As #Hanspassant noted in comments prefer Using statements to Dispose the resources. They provide a convenient and reliable way of disposing a resource.
I'm trying to make a few images do the nice slidey thingy that I've seen lots of Microsoft applications use. The one where the movement starts slow speeds up half way there and then comes to a nice slow stop in it's new location. I've got all the calculations figured out, getting and setting the picture box locations, Confirmation using console.writeline that the image locations are correct, and even a test run that works in a simplified format.
But in the full blown version It's not repainting the image. In fact, it looks like nothing has happened at all while the script is running. I've tried Me.Refresh(), Invalidate(), Timer.Enabled = True/False, and Me.Update(). None of which have worked. The last step is the most frustrating: I'm calling my SetPanelLocation() method at the end to ensure that the panel ends up in the final location regardless of if the movement worked. Nothing happens on this call either, even though immediately after this routine fails I can call the same method from another user event and it starts working again like nothing was wrong.
I'm creating my own PictureBox class called clsFeedImageBox which inherits PictureBox that includes this functionality (along with other features). Each image is only 300x225 pixels so they're not massive images that take a lot of time to redraw. Each instance of this class is in a common Forms.SplitterPanel. I use a lot of comments out of habit so i left them in here, maybe they'll add some light.
Public Class clsFeedImgBox
Inherits PictureBox
Private iRank As Integer 'rank in whatever feed this file gets put in
Private iRankTarget As Integer 'rank to move to when rank feed event starts
Private iTopStart As Integer 'starting top location before feed event
Private iTopTarget As Integer 'final Top location after feed event
Private WithEvents tMyTimer As New System.Timers.Timer
Private WithEvents oParent As FeedBase 'splitter panel, all location info comes from the parent
Public Sub New(ByRef sender As FeedBase, ByVal rank as Integer)
'set objects
oParent = sender
'set .Image property to pre-made thumbnail
Image.FromFile(ThumbPath) 'ThumbPath is a property which is set by this point (some code has been removed)
'setup initial position
setPanelLocation(rank)
'set autosize
Me.SizeMode = PictureBoxSizeMode.StretchImage
'set Image Scroll timer interval to 20 fps (1000 / 20 = 50)
tMyTimer.Interval = 50
End Sub
Public Sub scroll(ByVal newRank As Integer)
'setPanelLocation(newRank) <== this works, timed movements don't
iRankTarget = newRank
iTopStart = Me.Top
iTopTarget = oParent.ImgTop(newRank) 'gets an integer for the new Top location
tMyTimer.Start()
End Sub
Private Sub myScrollStep() Handles tMyTimer.Elapsed
'tMyTimer.Enabled = False 'this idea with the enabled = True at the end didn't work
iTickCount += 1
Dim iScrollPerc As Integer 'scroll % between Start and End * 100
iScrollPerc = oParent.ScrollStep(iTickCount, Rank) 'this part works
Console.WriteLine(strThumbName & " scrollPerc: " & iScrollPerc.ToString)
If iScrollPerc >= 100 Then
'scroll event complete
Console.WriteLine(strThumbName & " SetFinalLocation")
Me.setPanelLocation(iRankTarget) '<== This line doesn't work here, but works when called by other means
'stop Feed updates
tMyTimer.Stop()
'reset iTickCount for next movement
iTickCount = 0
Else
'scrolling still going
Dim newTop As Integer
newTop = Math.Round(iTopTarget - (((100 - iScrollPerc) * (iTopTarget - iTopStart)) / 100)) 'this part works
'Console.WriteLine(strThumbName & " TopTarget: " & newTop)
Me.Top = newTop 'Nothing happens here
End If
'Me.Left = oParent.ImgLeft
'Me.Width = oParent.ImgWidth
'Me.Height = oParent.ImgHeight 'that didn't work
'Me.Refresh() 'this didn't work
'Invalidate() 'this didn't do much good either
'Me.Update() 'Aaaaand no cigar, time for StackOverflow
'tMyTimer.Enabled = True
End Sub
Public Sub setPanelLocation(ByVal rank As Integer)
iRank = rank
Me.MyRePaint()
End Sub
Public Sub MyRePaint()
'repaint image box with everything in it's current rank
Me.Left = oParent.ImgLeft
Me.Top = oParent.ImgTop(iRank)
Me.Width = oParent.ImgWidth
Me.Height = oParent.ImgHeight
End Sub
End Class
What gives? There must be some inner workings of VB.NET that will help me figure this out. I'm using VS 2012 and Win8
You could make a WPF application and use a Slider control instead of "manually" making a slider with planes, picture boxes, etc, etc.